{
  "info": {
    "name": "EDK Enterprise REST",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Common developer journeys against an EDK Enterprise deployment.\n\n1. **Platform operator**: list the tenants or register one.\n2. **Tenant owner**: create a service client for your applications (or do it in the admin console).\n3. **Tenant APIs**: everything else, with the service client's OAuth2 token: keys and DIDs, Azure Key Vault, issuing and verifying credentials, status lists, trust domains and a Keycloak wallet login.\n\nSet two values in the environment: `baseDomain` (for example `example.com`) and `tenantSlug` (for example `acme`). The platform is at `https://platform.<baseDomain>` and the tenant at `https://<tenantSlug>.<baseDomain>`; every URL follows from these two. Each folder sets up its own OAuth2 token: open the folder's **Authorization** tab and choose **Get New Access Token**.\n\nIds that a request needs are captured from earlier list or create responses, so start a folder at its first request. Folders marked optional need extra inputs and skip their requests until you set them.\n\nDocumentation: https://docs.sphereon.com/edk/deployment/onboarding-walkthrough"
  },
  "auth": {
    "type": "noauth"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// All URLs are built from two environment values: baseDomain and tenantSlug.",
          "// The platform lives at platform.<baseDomain>, a tenant at <tenantSlug>.<baseDomain>.",
          "const baseDomain = String(pm.environment.get('baseDomain') || pm.collectionVariables.get('baseDomain') || '').trim();",
          "const tenantSlug = String(pm.environment.get('tenantSlug') || pm.collectionVariables.get('tenantSlug') || '').trim();",
          "if (!baseDomain || /^https?:/i.test(baseDomain) || baseDomain.includes('/')) {",
          "  throw new Error('Set baseDomain in your environment to a bare domain such as example.com (add :port only if your gateway uses one).');",
          "}",
          "// Identifiers captured from responses belong to one tenant. Forget them when you switch tenantSlug.",
          "const tenantScoped = pm.collectionVariables.get('tenantScopedVariables').split(',');",
          "if (pm.collectionVariables.get('capturedForTenant') !== tenantSlug) {",
          "  for (const key of tenantScoped) pm.collectionVariables.set(key, '');",
          "  pm.collectionVariables.set('capturedForTenant', tenantSlug);",
          "}",
          "if (tenantSlug) {",
          "  const did = 'did:web:' + (tenantSlug + '.' + baseDomain).replace(/:/g, '%3A');",
          "  pm.collectionVariables.set('did', did);",
          "  pm.collectionVariables.set('didEncoded', encodeURIComponent(did));",
          "}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "1. Platform operator",
      "description": "Requests on `platform.<baseDomain>` that only the platform operator may make.\n\n**Sign in:** open this folder, go to **Authorization** and choose **Get New Access Token**. Sign in with the platform operator account in the window that opens, then choose **Use Token**.\n\nDocumentation: https://docs.sphereon.com/edk/deployment/onboarding-walkthrough",
      "auth": {
        "type": "oauth2",
        "oauth2": [
          {
            "key": "tokenName",
            "value": "Platform operator",
            "type": "string"
          },
          {
            "key": "grant_type",
            "value": "authorization_code_with_pkce",
            "type": "string"
          },
          {
            "key": "authUrl",
            "value": "https://platform.{{baseDomain}}/authorize",
            "type": "string"
          },
          {
            "key": "accessTokenUrl",
            "value": "https://platform.{{baseDomain}}/token",
            "type": "string"
          },
          {
            "key": "clientId",
            "value": "platform-operator-cli",
            "type": "string"
          },
          {
            "key": "redirect_uri",
            "value": "https://platform.{{baseDomain}}/admin-console/callback",
            "type": "string"
          },
          {
            "key": "useBrowser",
            "value": false,
            "type": "boolean"
          },
          {
            "key": "scope",
            "value": "openid",
            "type": "string"
          },
          {
            "key": "challengeAlgorithm",
            "value": "S256",
            "type": "string"
          },
          {
            "key": "state",
            "value": "{{$guid}}",
            "type": "string"
          },
          {
            "key": "client_authentication",
            "value": "body",
            "type": "string"
          },
          {
            "key": "addTokenTo",
            "value": "header",
            "type": "string"
          },
          {
            "key": "headerPrefix",
            "value": "Bearer",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "Tenants",
          "description": "List the tenants, or register a new one. You rarely create tenants: most of the time you set `tenantSlug` to an existing tenant and move on to folder 2 or 3.",
          "item": [
            {
              "name": "1. List tenants",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const tenants = pm.response.json().data || [];",
                      "  console.log('Tenants: ' + tenants.map(t => t.slug + ' (' + t.status + ')').join(', '));",
                      "  const slug = pm.variables.replaceIn('{{tenantSlug}}');",
                      "  const tenant = tenants.find(t => t.slug === slug);",
                      "  if (tenant) {",
                      "    pm.collectionVariables.set('tenantId', tenant.id);",
                      "    console.log('tenantId ' + tenant.id + ' for ' + slug);",
                      "  } else {",
                      "    console.log('No tenant with slug ' + slug + ' yet. Register it with the next request.');",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://platform.{{baseDomain}}/api/platform/admin/v1/tenants?page=0&size=100",
                "description": "Lists the tenants on this installation. When one has the slug in `tenantSlug`, its id is stored in `tenantId` and you can skip registration."
              },
              "response": []
            },
            {
              "name": "2. Register tenant",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.collectionVariables.get('tenantId')) {",
                      "  console.log('Skipped: tenant ' + pm.variables.replaceIn('{{tenantSlug}}') + ' already exists (' + pm.collectionVariables.get('tenantId') + ').');",
                      "  pm.execution.skipRequest();",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  const result = pm.response.json();",
                      "  pm.collectionVariables.set('tenantId', result.tenant.id);",
                      "  pm.collectionVariables.set('onboardingCorrelationId', result.correlationId);",
                      "  if (result.delivery && result.delivery.manualActivationLink) {",
                      "    pm.collectionVariables.set('ownerActivationLink', result.delivery.manualActivationLink);",
                      "    console.log('Open this link in a browser to activate the tenant owner: ' + result.delivery.manualActivationLink);",
                      "  } else {",
                      "    console.log('The owner invitation was sent to ' + pm.variables.replaceIn('admin@{{tenantSlug}}.example') + '.');",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://platform.{{baseDomain}}/api/platform/admin/v1/tenants",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"tenant\": {\n    \"tenantType\": \"organization\",\n    \"name\": \"{{tenantName}}\",\n    \"description\": \"{{tenantName}} issuing and verification tenant\",\n    \"slug\": \"{{tenantSlug}}\",\n    \"initialPlatformSubdomain\": true\n  },\n  \"contacts\": {\n    \"technical\": {\n      \"email\": \"admin@{{tenantSlug}}.example\",\n      \"displayName\": \"{{tenantName}} administrator\"\n    },\n    \"administrativeSameAsTechnical\": true,\n    \"ownerAdmin\": {\n      \"source\": \"technical\"\n    }\n  },\n  \"login\": {\n    \"enabled\": true,\n    \"defaultAuthorizationServerRequired\": true\n  },\n  \"provisioning\": {\n    \"issuer\": true,\n    \"verifier\": true,\n    \"keysAndDids\": true,\n    \"sampleData\": true\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Registers the tenant `tenantSlug` and its owner. The response carries `delivery.manualActivationLink` when no email transport is configured: open it in a browser to choose the owner password. The owner signs in with the technical contact email."
              },
              "response": []
            },
            {
              "name": "3. Get onboarding status",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (!pm.collectionVariables.get('onboardingCorrelationId')) {",
                      "  console.log('Skipped: no registration in this session.');",
                      "  pm.execution.skipRequest();",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  console.log('Onboarding ' + pm.response.json().status);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://platform.{{baseDomain}}/api/platform/admin/v1/tenant-onboarding/{{onboardingCorrelationId}}",
                "description": "Repeat until `status` is `COMPLETED`. Onboarding provisions the default authorization server, issuer, verifier, keys, DID and sample data."
              },
              "response": []
            },
            {
              "name": "4. Get tenant",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://platform.{{baseDomain}}/api/platform/admin/v1/tenants/{{tenantId}}"
              },
              "response": []
            }
          ]
        },
        {
          "name": "Child tenants (optional)",
          "description": "A tenant can have child tenants. A child is a full tenant with its own host `<childSlug>.<baseDomain>`: use a separate environment for it.",
          "item": [
            {
              "name": "1. List child tenants",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://platform.{{baseDomain}}/api/platform/admin/v1/tenants/{{tenantId}}/children?page=0&size=100"
              },
              "response": []
            },
            {
              "name": "2. Register a child tenant",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  console.log('Child tenant ' + pm.response.json().tenant.slug + ' at ' + pm.response.json().tenantUrl);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://platform.{{baseDomain}}/api/platform/admin/v1/tenants",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"tenant\": {\n    \"tenantType\": \"organization\",\n    \"name\": \"{{tenantName}} EU\",\n    \"description\": \"{{tenantName}} EU child tenant\",\n    \"slug\": \"{{tenantSlug}}-eu\",\n    \"parentTenantId\": \"{{tenantId}}\",\n    \"initialPlatformSubdomain\": true\n  },\n  \"contacts\": {\n    \"technical\": {\n      \"email\": \"admin@{{tenantSlug}}-eu.example\",\n      \"displayName\": \"{{tenantName}} EU administrator\"\n    },\n    \"administrativeSameAsTechnical\": true,\n    \"ownerAdmin\": {\n      \"source\": \"technical\"\n    }\n  },\n  \"login\": {\n    \"enabled\": true,\n    \"defaultAuthorizationServerRequired\": true\n  },\n  \"provisioning\": {\n    \"issuer\": true,\n    \"verifier\": true,\n    \"keysAndDids\": true,\n    \"sampleData\": true\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Share an Azure Key Vault with tenants (optional)",
          "description": "The platform registers an Azure Key Vault once and offers it to tenants. Tenants then enable it (folder 3, Azure Key Vault) without ever seeing the vault credentials. Set the `azure*` inputs in your environment. Documentation: https://docs.sphereon.com/edk/guides/azure-kms-byok-byoc",
          "item": [
            {
              "name": "1. List platform KMS resources",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "let skipped = false;",
                      "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                      "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                      "  if (!value || value.startsWith('{{')) {",
                      "    console.log('Skipped: set ' + key + ' in your environment to share an Azure Key Vault.');",
                      "    pm.execution.skipRequest();",
                      "    skipped = true;",
                      "    break;",
                      "  }",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const resource = (pm.response.json().resources || []).find(r => r.providerId === pm.variables.replaceIn('{{sharedAzureProviderId}}') && !['RETIRED', 'DETACHED'].includes(r.state));",
                      "  pm.collectionVariables.set('sharedAzureResourceHandle', resource ? resource.handle : '');",
                      "  if (resource) {",
                      "    pm.collectionVariables.set('sharedAzureResourceVersion', String(resource.resourceVersion));",
                      "    pm.collectionVariables.set('sharedAzureCredentialSecretRef', resource.credentialSecretRef);",
                      "    console.log('The shared vault is already registered as ' + resource.handle + ' (' + resource.state + ').');",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://platform.{{baseDomain}}/api/platform/config/v1/tenants/platform/kms/resources"
              },
              "response": []
            },
            {
              "name": "2. Register the vault",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "let skipped = false;",
                      "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                      "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                      "  if (!value || value.startsWith('{{')) {",
                      "    console.log('Skipped: set ' + key + ' in your environment to share an Azure Key Vault.');",
                      "    pm.execution.skipRequest();",
                      "    skipped = true;",
                      "    break;",
                      "  }",
                      "}",
                      "if (!skipped && pm.collectionVariables.get('sharedAzureResourceHandle')) pm.execution.skipRequest();"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  const resource = pm.response.json();",
                      "  pm.collectionVariables.set('sharedAzureResourceHandle', resource.handle);",
                      "  pm.collectionVariables.set('sharedAzureResourceVersion', String(resource.resourceVersion));",
                      "  pm.collectionVariables.set('sharedAzureCredentialSecretRef', resource.credentialSecretRef);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://platform.{{baseDomain}}/api/platform/config/v1/tenants/platform/kms/resources",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{sharedAzureProviderId}}\",\n  \"kind\": \"AZURE_KEY_VAULT\",\n  \"displayName\": \"Shared Azure Key Vault\",\n  \"configuration\": {\n    \"vaultUri\": \"{{azureVaultUri}}\",\n    \"tenantId\": \"{{azureDirectoryId}}\",\n    \"clientId\": \"{{azureClientId}}\",\n    \"hsmType\": \"KEYVAULT\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "3. Store the Entra client secret",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "let skipped = false;",
                      "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                      "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                      "  if (!value || value.startsWith('{{')) {",
                      "    console.log('Skipped: set ' + key + ' in your environment to share an Azure Key Vault.');",
                      "    pm.execution.skipRequest();",
                      "    skipped = true;",
                      "    break;",
                      "  }",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('sharedAzureResourceVersion', String(pm.response.json().resourceVersion));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://platform.{{baseDomain}}/api/platform/config/v1/tenants/platform/kms/resources/{{sharedAzureResourceHandle}}/credentials/azure-key-vault",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"expectedResourceVersion\": \"{{sharedAzureResourceVersion}}\",\n  \"clientSecretRef\": \"{{sharedAzureCredentialSecretRef}}\",\n  \"clientSecret\": \"{{azureClientSecret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "The secret is written once and never returned. Repeat this request to rotate it."
              },
              "response": []
            },
            {
              "name": "4. Validate the vault",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "let skipped = false;",
                      "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                      "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                      "  if (!value || value.startsWith('{{')) {",
                      "    console.log('Skipped: set ' + key + ' in your environment to share an Azure Key Vault.');",
                      "    pm.execution.skipRequest();",
                      "    skipped = true;",
                      "    break;",
                      "  }",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  console.log('Vault ' + pm.response.json().state + ', health ' + pm.response.json().health);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [],
                "url": "https://platform.{{baseDomain}}/api/platform/config/v1/tenants/platform/kms/resources/{{sharedAzureResourceHandle}}/validate"
              },
              "response": []
            },
            {
              "name": "5. Offer the vault to the tenant",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "let skipped = false;",
                      "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\",\"tenantId\"]) {",
                      "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                      "  if (!value || value.startsWith('{{')) {",
                      "    console.log('Skipped: set ' + key + ' in your environment to share an Azure Key Vault.');",
                      "    pm.execution.skipRequest();",
                      "    skipped = true;",
                      "    break;",
                      "  }",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('sharedAzureResourceVersion', String(pm.response.json().resourceVersion));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://platform.{{baseDomain}}/api/platform/config/v1/tenants/platform/kms/resources/{{sharedAzureResourceHandle}}/sharing",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"fulfillment\": \"SHARED_INSTANCE\",\n  \"tenantIds\": [\"{{tenantId}}\"],\n  \"suggestedDefault\": false,\n  \"expectedResourceVersion\": {{sharedAzureResourceVersion}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "`tenantIds` lists every tenant that may enable this vault. Each tenant gets its own key namespace; tenants never see each other's keys."
              },
              "response": []
            }
          ]
        }
      ]
    },
    {
      "name": "2. Tenant owner: create a service client",
      "description": "Your applications call the tenant APIs with a **service client**: an OAuth2 client of the tenant's own authorization server that uses the client-credentials grant, with no user involved. Create it here once as the tenant owner, or in the admin console under **Authorization server > Clients**. Either way, put its id and secret in `serviceClientId` and `serviceClientSecret`.\n\n**Sign in:** open this folder, go to **Authorization** and choose **Get New Access Token**. Sign in as the tenant owner (the account you activated with the owner link), then choose **Use Token**.\n\nDocumentation: https://docs.sphereon.com/edk/guides/journeys/platform-foundations/authorization-server",
      "auth": {
        "type": "oauth2",
        "oauth2": [
          {
            "key": "tokenName",
            "value": "Tenant owner",
            "type": "string"
          },
          {
            "key": "grant_type",
            "value": "authorization_code_with_pkce",
            "type": "string"
          },
          {
            "key": "authUrl",
            "value": "https://{{tenantSlug}}.{{baseDomain}}/as/{{tenantSlug}}/authorize",
            "type": "string"
          },
          {
            "key": "accessTokenUrl",
            "value": "https://{{tenantSlug}}.{{baseDomain}}/as/{{tenantSlug}}/token",
            "type": "string"
          },
          {
            "key": "clientId",
            "value": "developer-postman",
            "type": "string"
          },
          {
            "key": "redirect_uri",
            "value": "https://oauth.pstmn.io/v1/browser-callback",
            "type": "string"
          },
          {
            "key": "useBrowser",
            "value": false,
            "type": "boolean"
          },
          {
            "key": "scope",
            "value": "openid profile email",
            "type": "string"
          },
          {
            "key": "challengeAlgorithm",
            "value": "S256",
            "type": "string"
          },
          {
            "key": "state",
            "value": "{{$guid}}",
            "type": "string"
          },
          {
            "key": "client_authentication",
            "value": "body",
            "type": "string"
          },
          {
            "key": "addTokenTo",
            "value": "header",
            "type": "string"
          },
          {
            "key": "headerPrefix",
            "value": "Bearer",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "1. List authorization servers",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if ([200].includes(pm.response.code)) {",
                  "  const slug = pm.variables.replaceIn('{{tenantSlug}}');",
                  "  const servers = pm.response.json().items || [];",
                  "  const server = servers.find(s => (s.endpoints || []).some(e => e.pathPrefix === '/as/' + slug)) || servers[0];",
                  "  pm.collectionVariables.set('authorizationServerId', server.id);",
                  "  pm.collectionVariables.set('tenantId', server.links.settings.split('/tenants/')[1].split('/')[0]);",
                  "  console.log('Default authorization server ' + server.id + ', tenantId ' + pm.collectionVariables.get('tenantId'));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oauth2/v1/servers",
            "description": "The tenant's default authorization server is the one served at `/as/<tenantSlug>`. Its id, and the tenant id, are stored for the next requests."
          },
          "response": []
        },
        {
          "name": "2. List service clients",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if ([200].includes(pm.response.code)) {",
                  "  const clients = pm.response.json().clients || [];",
                  "  console.log('Clients: ' + (clients.map(c => c.clientId).join(', ') || 'none'));",
                  "  const exists = clients.some(c => c.clientId === pm.variables.replaceIn('{{serviceClientId}}'));",
                  "  pm.collectionVariables.set('serviceClientExists', String(exists));",
                  "  if (exists) console.log(pm.variables.replaceIn('{{serviceClientId}}') + ' already exists. Use request 4 if you no longer have its secret.');",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{authorizationServerId}}/clients"
          },
          "response": []
        },
        {
          "name": "3. Create the service client",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.collectionVariables.get('serviceClientExists') === 'true') {",
                  "  console.log('Skipped: the service client already exists.');",
                  "  pm.execution.skipRequest();",
                  "}",
                  "if (!String(pm.variables.replaceIn('{{serviceClientSecret}}') || '').trim() || pm.variables.replaceIn('{{serviceClientSecret}}').startsWith('{{')) {",
                  "  const cryptoJs = require('crypto-js');",
                  "  const secret = cryptoJs.lib.WordArray.random(32).toString(cryptoJs.enc.Base64).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "  if (pm.environment.has('serviceClientSecret')) pm.environment.set('serviceClientSecret', secret); else pm.collectionVariables.set('serviceClientSecret', secret);",
                  "  console.log('Generated a new serviceClientSecret and stored it in your ' + (pm.environment.has('serviceClientSecret') ? 'environment' : 'collection variables') + '.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                  "if ([201].includes(pm.response.code)) {",
                  "  pm.collectionVariables.set('serviceClientExists', 'true');",
                  "  console.log('Service client created. Open folder 3 and get a token with it.');",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{authorizationServerId}}/clients",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"clientId\": \"{{serviceClientId}}\",\n  \"clientName\": \"Postman service client\",\n  \"clientType\": \"confidential\",\n  \"enabled\": true,\n  \"grantTypes\": [\n    \"client_credentials\"\n  ],\n  \"tokenEndpointAuthMethod\": \"client_secret_post\",\n  \"principalRoles\": [\n    \"tenant-admin\"\n  ],\n  \"defaultAccessTokenAudience\": \"enterprise-platform\",\n  \"allowedAccessTokenAudiences\": [\n    \"enterprise-platform\",\n    \"enterprise-tenant-kms\",\n    \"enterprise-tenant-did\",\n    \"enterprise-tenant-as\",\n    \"enterprise-issuer\",\n    \"enterprise-verifier\",\n    \"enterprise-blob\"\n  ],\n  \"clientCredential\": {\n    \"method\": \"client_secret_post\",\n    \"clientId\": \"{{serviceClientId}}\",\n    \"secretValue\": \"{{serviceClientSecret}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Creates a confidential client with the client-credentials grant and the `tenant-admin` role. `allowedAccessTokenAudiences` names the tenant services its tokens may call. The secret is set by you and never returned: when `serviceClientSecret` is empty, a random one is generated and stored in your environment."
          },
          "response": []
        },
        {
          "name": "4. Replace the service client secret",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!String(pm.variables.replaceIn('{{serviceClientSecret}}') || '').trim() || pm.variables.replaceIn('{{serviceClientSecret}}').startsWith('{{')) {",
                  "  const cryptoJs = require('crypto-js');",
                  "  const secret = cryptoJs.lib.WordArray.random(32).toString(cryptoJs.enc.Base64).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "  if (pm.environment.has('serviceClientSecret')) pm.environment.set('serviceClientSecret', secret); else pm.collectionVariables.set('serviceClientSecret', secret);",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{authorizationServerId}}/clients/{{serviceClientId}}",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"clientCredential\": {\n    \"method\": \"client_secret_post\",\n    \"clientId\": \"{{serviceClientId}}\",\n    \"secretValue\": \"{{serviceClientSecret}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Secrets cannot be read back. Send this when you lost the secret of an existing client: it stores `serviceClientSecret` (generated when empty) as the new secret."
          },
          "response": []
        }
      ]
    },
    {
      "name": "3. Tenant APIs",
      "description": "Everything your applications do on `<tenantSlug>.<baseDomain>`, with the service client's token.\n\n**Get a token:** set `serviceClientId` and `serviceClientSecret` (folder 2 fills in the secret when it creates the client). Open this folder, go to **Authorization**, choose **Get New Access Token**, then **Use Token**. Postman asks for one token that is valid for all tenant services; get a new one when it expires after an hour.\n\nEach folder starts by listing what exists, and stores the ids that later requests use.",
      "auth": {
        "type": "oauth2",
        "oauth2": [
          {
            "key": "tokenName",
            "value": "Tenant service client",
            "type": "string"
          },
          {
            "key": "grant_type",
            "value": "client_credentials",
            "type": "string"
          },
          {
            "key": "accessTokenUrl",
            "value": "https://{{tenantSlug}}.{{baseDomain}}/as/{{tenantSlug}}/token",
            "type": "string"
          },
          {
            "key": "clientId",
            "value": "{{serviceClientId}}",
            "type": "string"
          },
          {
            "key": "clientSecret",
            "value": "{{serviceClientSecret}}",
            "type": "string"
          },
          {
            "key": "client_authentication",
            "value": "body",
            "type": "string"
          },
          {
            "key": "addTokenTo",
            "value": "header",
            "type": "string"
          },
          {
            "key": "headerPrefix",
            "value": "Bearer",
            "type": "string"
          },
          {
            "key": "tokenRequestParams",
            "value": [
              {
                "key": "audience",
                "value": "enterprise-platform",
                "enabled": true,
                "send_as": "request_body"
              },
              {
                "key": "audience",
                "value": "enterprise-tenant-kms",
                "enabled": true,
                "send_as": "request_body"
              },
              {
                "key": "audience",
                "value": "enterprise-tenant-did",
                "enabled": true,
                "send_as": "request_body"
              },
              {
                "key": "audience",
                "value": "enterprise-tenant-as",
                "enabled": true,
                "send_as": "request_body"
              },
              {
                "key": "audience",
                "value": "enterprise-issuer",
                "enabled": true,
                "send_as": "request_body"
              },
              {
                "key": "audience",
                "value": "enterprise-verifier",
                "enabled": true,
                "send_as": "request_body"
              },
              {
                "key": "audience",
                "value": "enterprise-blob",
                "enabled": true,
                "send_as": "request_body"
              }
            ],
            "type": "any"
          }
        ]
      },
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (!pm.collectionVariables.get('tenantId')) {",
              "  const cryptoJs = require('crypto-js');",
              "  const clientId = pm.variables.replaceIn('{{serviceClientId}}');",
              "  const clientSecret = pm.variables.replaceIn('{{serviceClientSecret}}');",
              "  if (!clientId || !clientSecret || clientSecret.startsWith('{{')) {",
              "    throw new Error('Set serviceClientId and serviceClientSecret in your environment, or run folder 2 to create the service client.');",
              "  }",
              "  pm.sendRequest({",
              "    url: pm.variables.replaceIn('https://{{tenantSlug}}.{{baseDomain}}/as/{{tenantSlug}}/token'),",
              "    method: 'POST',",
              "    header: {'Content-Type': 'application/x-www-form-urlencoded'},",
              "    body: {mode: 'urlencoded', urlencoded: [",
              "      {key: 'grant_type', value: 'client_credentials'},",
              "      {key: 'client_id', value: clientId},",
              "      {key: 'client_secret', value: clientSecret},",
              "      {key: 'audience', value: 'enterprise-tenant-as'}",
              "    ]}",
              "  }, (error, response) => {",
              "    if (error) throw error;",
              "    const token = response.json().access_token;",
              "    if (!token) throw new Error('The service client could not get a token: ' + response.text());",
              "    const part = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');",
              "    const claims = JSON.parse(cryptoJs.enc.Base64.parse(part + '='.repeat((4 - part.length % 4) % 4)).toString(cryptoJs.enc.Utf8));",
              "    pm.collectionVariables.set('tenantId', claims.tenant_id);",
              "    console.log('tenantId ' + claims.tenant_id + ' (from the service client token)');",
              "  });",
              "}"
            ]
          }
        }
      ],
      "item": [
        {
          "name": "Authorization server",
          "description": "Read the tenant's authorization servers and their clients. The default one issues the service client's tokens.",
          "item": [
            {
              "name": "1. List authorization servers",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const slug = pm.variables.replaceIn('{{tenantSlug}}');",
                      "  const servers = pm.response.json().items || [];",
                      "  const server = servers.find(s => (s.endpoints || []).some(e => e.pathPrefix === '/as/' + slug)) || servers[0];",
                      "  pm.collectionVariables.set('authorizationServerId', server.id);",
                      "  console.log('Authorization servers: ' + servers.map(s => s.id + ' ' + s.endpoints.map(e => e.pathPrefix).join(' ')).join(', '));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oauth2/v1/servers"
              },
              "response": []
            },
            {
              "name": "2. Get authorization server settings",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{authorizationServerId}}"
              },
              "response": []
            },
            {
              "name": "3. List clients",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{authorizationServerId}}/clients"
              },
              "response": []
            },
            {
              "name": "4. Get the service client",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{authorizationServerId}}/clients/{{serviceClientId}}"
              },
              "response": []
            },
            {
              "name": "5. Read OpenID configuration",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/as/{{tenantSlug}}/.well-known/openid-configuration",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Keys and DIDs",
          "description": "The tenant's key management (KMS) and its `did:web` identifier. A KMS **resource** is the configured backend (software, Azure Key Vault, AWS KMS), addressed by a `krh_...` handle. At runtime you use its `providerId` plus a key `alias`.",
          "item": [
            {
              "name": "1. List KMS offerings",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/offerings"
              },
              "response": []
            },
            {
              "name": "2. List KMS resources",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const resources = pm.response.json().resources || [];",
                      "  console.log('KMS resources: ' + resources.map(r => r.providerId + ' ' + r.kind + ' ' + r.state).join(', '));",
                      "  const resource = resources.find(r => r.state === 'CONFIGURED') || resources[0];",
                      "  if (resource) {",
                      "    pm.collectionVariables.set('kmsResourceHandle', resource.handle);",
                      "    pm.collectionVariables.set('kmsProviderId', resource.providerId);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/resources"
              },
              "response": []
            },
            {
              "name": "3. Validate the KMS resource",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/resources/{{kmsResourceHandle}}/validate"
              },
              "response": []
            },
            {
              "name": "4. List runtime providers",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/providers"
              },
              "response": []
            },
            {
              "name": "5. Generate a signing key",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (!pm.collectionVariables.get('keyAlias')) pm.collectionVariables.set('keyAlias', 'postman-key-' + Date.now());"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/providers/{{kmsProviderId}}/keys",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"alias\": \"{{keyAlias}}\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\n    \"sign\"\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "6. List keys",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/providers/{{kmsProviderId}}/keys"
              },
              "response": []
            },
            {
              "name": "7. Get the key",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/keys/{{keyAlias}}?providerId={{kmsProviderId}}"
              },
              "response": []
            },
            {
              "name": "8. Sign data",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('signature', pm.response.json().signature);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/signatures/raw/create",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"keyInfo\": {\n    \"providerId\": \"{{kmsProviderId}}\",\n    \"alias\": \"{{keyAlias}}\"\n  },\n  \"input\": \"aGVsbG8gd29ybGQ=\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "`input` is base64. The signature is returned base64 encoded."
              },
              "response": []
            },
            {
              "name": "9. Verify the signature",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  pm.test('signature is valid', () => pm.expect(pm.response.json().isValid).to.eql(true));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/signatures/raw/verify",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"keyInfo\": {\n    \"providerId\": \"{{kmsProviderId}}\",\n    \"alias\": \"{{keyAlias}}\"\n  },\n  \"input\": \"aGVsbG8gd29ybGQ=\",\n  \"signature\": \"{{signature}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "10. List DIDs",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/did/v1/identifiers"
              },
              "response": []
            },
            {
              "name": "11. Get the tenant DID",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/did/v1/identifiers/{{didEncoded}}"
              },
              "response": []
            },
            {
              "name": "12. List its verification methods",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/did/v1/identifiers/{{didEncoded}}/verification-methods"
              },
              "response": []
            },
            {
              "name": "13. Resolve the public DID document",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/.well-known/did.json",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Azure Key Vault (optional)",
          "description": "Sign with keys in Azure Key Vault instead of the built-in software KMS. Private keys never leave Azure. Documentation: https://docs.sphereon.com/edk/guides/azure-kms-byok-byoc",
          "item": [
            {
              "name": "Use your own vault",
              "description": "Connect an Azure Key Vault that your tenant owns. Set `azureVaultUri`, `azureDirectoryId` (the Entra tenant id), `azureClientId` and `azureClientSecret` of an app registration that may use the vault's keys. Set `azureKeyName` to register an existing key (BYOK), and `azureCertificateChain` (base64 DER of its certificate) to register its certificate (BYOC). Keys you register are only referenced: removing them from VDX never deletes them from Azure.",
              "item": [
                {
                  "name": "1. List KMS resources",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to connect an Azure Key Vault.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const resource = (pm.response.json().resources || []).find(r => r.providerId === pm.variables.replaceIn('{{azureProviderId}}') && !['RETIRED', 'DETACHED'].includes(r.state));",
                          "  pm.collectionVariables.set('azureResourceHandle', resource ? resource.handle : '');",
                          "  if (resource) {",
                          "    pm.collectionVariables.set('azureResourceVersion', String(resource.resourceVersion));",
                          "    pm.collectionVariables.set('azureCredentialSecretRef', resource.credentialSecretRef);",
                          "    console.log('The vault is already connected as ' + resource.handle + ' (' + resource.state + ').');",
                          "  }",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/resources"
                  },
                  "response": []
                },
                {
                  "name": "2. Connect the vault",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to connect an Azure Key Vault.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped && pm.collectionVariables.get('azureResourceHandle')) pm.execution.skipRequest();"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                          "if ([201].includes(pm.response.code)) {",
                          "  const resource = pm.response.json();",
                          "  pm.collectionVariables.set('azureResourceHandle', resource.handle);",
                          "  pm.collectionVariables.set('azureResourceVersion', String(resource.resourceVersion));",
                          "  pm.collectionVariables.set('azureCredentialSecretRef', resource.credentialSecretRef);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/resources",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"providerId\": \"{{azureProviderId}}\",\n  \"kind\": \"AZURE_KEY_VAULT\",\n  \"displayName\": \"Azure Key Vault\",\n  \"configuration\": {\n    \"vaultUri\": \"{{azureVaultUri}}\",\n    \"tenantId\": \"{{azureDirectoryId}}\",\n    \"clientId\": \"{{azureClientId}}\",\n    \"hsmType\": \"KEYVAULT\"\n  }\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    }
                  },
                  "response": []
                },
                {
                  "name": "3. Store the Entra client secret",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to connect an Azure Key Vault.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('azureResourceVersion', String(pm.response.json().resourceVersion));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "PUT",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/resources/{{azureResourceHandle}}/credentials/azure-key-vault",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"expectedResourceVersion\": \"{{azureResourceVersion}}\",\n  \"clientSecretRef\": \"{{azureCredentialSecretRef}}\",\n  \"clientSecret\": \"{{azureClientSecret}}\"\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "The secret is written once and never returned. Repeat this request to rotate it."
                  },
                  "response": []
                },
                {
                  "name": "4. Validate the vault",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to connect an Azure Key Vault.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  console.log('Vault ' + pm.response.json().state + ', health ' + pm.response.json().health);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/resources/{{azureResourceHandle}}/validate"
                  },
                  "response": []
                },
                {
                  "name": "5. List runtime providers",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to connect an Azure Key Vault.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/providers"
                  },
                  "response": []
                },
                {
                  "name": "6. Register an existing key (BYOK)",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\",\"azureKeyName\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to register an existing Azure key.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/keys/register",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"providerId\": \"{{azureProviderId}}\",\n  \"alias\": \"{{azureKeyName}}\"\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    }
                  },
                  "response": []
                },
                {
                  "name": "7. Get the key",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\",\"azureKeyName\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to register an existing Azure key.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/keys/{{azureKeyName}}?providerId={{azureProviderId}}"
                  },
                  "response": []
                },
                {
                  "name": "8. Register its certificate chain (BYOC)",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\",\"azureKeyName\",\"azureCertificateChain\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to register the key certificate.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/certificates/register",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"providerId\": \"{{azureProviderId}}\",\n  \"alias\": \"{{azureKeyName}}-certificate\",\n  \"kind\": \"key_certificate_chain\",\n  \"source\": \"stored_public_material\",\n  \"linkedKeyAlias\": \"{{azureKeyName}}\",\n  \"certificateChain\": [\n    \"{{azureCertificateChain}}\"\n  ]\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    }
                  },
                  "response": []
                },
                {
                  "name": "9. Get the certificate chain",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\",\"azureKeyName\",\"azureCertificateChain\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to register the key certificate.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/certificate-chains/{{azureKeyName}}-certificate?providerId={{azureProviderId}}"
                  },
                  "response": []
                },
                {
                  "name": "10. Generate a key in the vault",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"azureVaultUri\",\"azureDirectoryId\",\"azureClientId\",\"azureClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to connect an Azure Key Vault.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/providers/{{azureProviderId}}/keys",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"alias\": \"postman-azure-{{$timestamp}}\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\n    \"sign\"\n  ]\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    }
                  },
                  "response": []
                }
              ]
            },
            {
              "name": "Use a vault shared by the platform",
              "description": "When the platform operator shared an Azure Key Vault with this tenant (folder 1), enable it here. The vault credentials stay with the platform.",
              "item": [
                {
                  "name": "1. List shared vaults offered to this tenant",
                  "event": [
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const providers = pm.response.json().providers || [];",
                          "  console.log('Offered: ' + (providers.map(p => p.providerId).join(', ') || 'none'));",
                          "  if (providers.length) pm.collectionVariables.set('sharedProviderId', providers[0].providerId);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/shared-providers"
                  },
                  "response": []
                },
                {
                  "name": "2. Enable the shared vault",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "if (!pm.collectionVariables.get('sharedProviderId')) {",
                          "  console.log('Skipped: no shared vault is offered to this tenant.');",
                          "  pm.execution.skipRequest();",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/kms/shared-providers/{{sharedProviderId}}:enable"
                  },
                  "response": []
                },
                {
                  "name": "3. List runtime providers",
                  "event": [
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/providers"
                  },
                  "response": []
                },
                {
                  "name": "4. Generate a key in the shared vault",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "if (!pm.collectionVariables.get('sharedProviderId')) {",
                          "  console.log('Skipped: no shared vault is offered to this tenant.');",
                          "  pm.execution.skipRequest();",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/kms/v1/providers/{{sharedProviderId}}/keys",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"alias\": \"postman-shared-{{$timestamp}}\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\n    \"sign\"\n  ]\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "The key lives in the platform vault, in a namespace of its own for this tenant. Use its alias with `providerId` set to the shared vault provider id, as with any other key."
                  },
                  "response": []
                }
              ]
            }
          ]
        },
        {
          "name": "Credential issuer",
          "description": "The tenant's OID4VCI credential issuer and the credential configurations it offers.",
          "item": [
            {
              "name": "1. List credential issuers",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const issuer = (pm.response.json().items || [])[0];",
                      "  pm.collectionVariables.set('issuerId', issuer.id);",
                      "  pm.collectionVariables.set('credentialIssuer', issuer.credentialIssuer);",
                      "  console.log('Issuer ' + issuer.id + ' at ' + issuer.credentialIssuer);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/instances"
              },
              "response": []
            },
            {
              "name": "2. Get the issuer",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/instances/{{issuerId}}"
              },
              "response": []
            },
            {
              "name": "3. List credential configurations",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const configurations = pm.response.json().credentialConfigurations || [];",
                      "  console.log('Credential configurations: ' + configurations.map(c => c.credentialConfigurationId + ' (' + c.format + ')').join(', '));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings"
              },
              "response": []
            },
            {
              "name": "4. Get the EuPid configuration",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const configuration = pm.response.json();",
                      "  pm.collectionVariables.set('signingKmsResourceHandle', configuration.kmsResourceHandle);",
                      "  pm.collectionVariables.set('signingKeyAlias', configuration.kmsKeyAlias);",
                      "  pm.collectionVariables.set('signingVerificationMethodId', configuration.signingVerificationMethodId);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/EuPid",
                "description": "The sample EuPid configuration. Its signing key selection (`kmsResourceHandle`, `kmsKeyAlias`, `signingVerificationMethodId`) is stored and reused when you define your own credential."
              },
              "response": []
            },
            {
              "name": "5. List authorization server bindings",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/authorization-server-bindings"
              },
              "response": []
            },
            {
              "name": "6. Read public issuer metadata",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/.well-known/openid-credential-issuer/oid4vci/{{tenantSlug}}",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Credential designs",
          "description": "How wallets display credentials: names, colors, logos and claim labels per language. The sample tenant has designs for EuPid and Mdl.",
          "item": [
            {
              "name": "1. List credential designs",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const designs = pm.response.json();",
                      "  console.log('Designs: ' + designs.map(d => d.alias + ' ' + d.id).join(', '));",
                      "  const design = designs.find(d => d.alias === 'eu-pid') || designs[0];",
                      "  if (design) pm.collectionVariables.set('designId', design.id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/credential-design/v1/designs/credentials"
              },
              "response": []
            },
            {
              "name": "2. Get a credential design",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/credential-design/v1/designs/credentials/{{designId}}"
              },
              "response": []
            },
            {
              "name": "3. List issuer designs",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/credential-design/v1/designs/issuers"
              },
              "response": []
            },
            {
              "name": "4. List render variants",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/credential-design/v1/designs/render/variants"
              },
              "response": []
            },
            {
              "name": "5. Read hosted type metadata (VCT)",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/public/schema/vct/EuPid",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Status lists",
          "description": "Status lists publish the revocation state of issued credentials. Credential configurations point to one by its `correlationId`, which is also the last segment of its public URL.",
          "item": [
            {
              "name": "1. List status lists",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const lists = pm.response.json().data || [];",
                      "  console.log('Status lists: ' + lists.map(l => l.correlationId + ' ' + l.id).join(', '));",
                      "  const list = lists.find(l => l.correlationId === 'eupid-revocation') || lists[0];",
                      "  if (list) pm.collectionVariables.set('statusListId', list.id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/statuslist/v1/statuslists?limit=100"
              },
              "response": []
            },
            {
              "name": "2. Get a status list",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/statuslist/v1/statuslists/{{statusListId}}"
              },
              "response": []
            },
            {
              "name": "3. Fetch the public status list token",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/public/statuslists/eupid-revocation",
                "auth": {
                  "type": "noauth"
                },
                "description": "What verifiers download. It is a signed token with the compressed bit string."
              },
              "response": []
            }
          ]
        },
        {
          "name": "Define your own credential",
          "description": "Adds an `EmployeeBadge` SD-JWT VC to the issuer: a status list for its revocation, a design for its display, and the credential configuration that ties them together. It signs with the same key as the sample EuPid. Issue it afterwards with **Issue credentials**.",
          "item": [
            {
              "name": "1. List credential issuers",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const issuer = (pm.response.json().items || [])[0];",
                      "  pm.collectionVariables.set('issuerId', issuer.id);",
                      "  pm.collectionVariables.set('credentialIssuer', issuer.credentialIssuer);",
                      "  console.log('Issuer ' + issuer.id + ' at ' + issuer.credentialIssuer);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/instances"
              },
              "response": []
            },
            {
              "name": "2. Get the issuer signing selection",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const configuration = pm.response.json();",
                      "  pm.collectionVariables.set('signingKmsResourceHandle', configuration.kmsResourceHandle);",
                      "  pm.collectionVariables.set('signingKeyAlias', configuration.kmsKeyAlias);",
                      "  pm.collectionVariables.set('signingVerificationMethodId', configuration.signingVerificationMethodId);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/EuPid"
              },
              "response": []
            },
            {
              "name": "3. List status lists",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const list = (pm.response.json().data || []).find(l => l.correlationId === 'employee-badge-status');",
                      "  pm.collectionVariables.set('employeeBadgeStatusListId', list ? list.id : '');",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/statuslist/v1/statuslists?limit=100"
              },
              "response": []
            },
            {
              "name": "4. Create the status list",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.collectionVariables.get('employeeBadgeStatusListId')) {",
                      "  console.log('Skipped: the employee-badge-status list exists.');",
                      "  pm.execution.skipRequest();",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('employeeBadgeStatusListId', pm.response.json().id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/statuslist/v1/statuslists",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"correlationId\": \"employee-badge-status\",\n  \"spec\": \"token_status_list\",\n  \"proofFormat\": \"jwt\",\n  \"purposes\": [\n    \"revocation\"\n  ],\n  \"issuer\": \"{{did}}\",\n  \"signingKeyMode\": \"did:web\",\n  \"kmsResourceHandle\": \"{{signingKmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{signingKeyAlias}}\",\n  \"signingVerificationMethodId\": \"{{signingVerificationMethodId}}\",\n  \"statusListUri\": \"https://{{tenantSlug}}.{{baseDomain}}/public/statuslists/employee-badge-status\",\n  \"length\": 131072,\n  \"bitsPerStatus\": 1\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "5. List credential designs",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const design = pm.response.json().find(d => d.alias === 'employee-badge');",
                      "  pm.collectionVariables.set('employeeBadgeDesignId', design ? design.id : '');",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/credential-design/v1/designs/credentials"
              },
              "response": []
            },
            {
              "name": "6. Create the credential design",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.collectionVariables.get('employeeBadgeDesignId')) {",
                      "  console.log('Skipped: the employee-badge design exists.');",
                      "  pm.execution.skipRequest();",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('employeeBadgeDesignId', pm.response.json().id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/credential-design/v1/designs/credentials",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"alias\": \"employee-badge\",\n  \"hostingMode\": \"LOCAL\",\n  \"bindings\": [\n    {\n      \"vct\": \"EmployeeBadge\",\n      \"vctHostingMode\": \"HOSTED\",\n      \"credentialConfigurationId\": \"EmployeeBadge\",\n      \"issuerId\": \"{{issuerId}}\",\n      \"issuerUri\": \"{{credentialIssuer}}\"\n    }\n  ],\n  \"credentialType\": {\n    \"format\": \"SD_JWT_VC\",\n    \"vct\": \"EmployeeBadge\"\n  },\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"name\": \"Employee badge\",\n      \"description\": \"Proves employment at {{tenantName}}\"\n    }\n  ],\n  \"claims\": [\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"given_name\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Given name\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 1,\n      \"sdPolicy\": \"ALLOWED\"\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"family_name\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Family name\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 2,\n      \"sdPolicy\": \"ALLOWED\"\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"employee_id\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Employee number\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 3,\n      \"sdPolicy\": \"ALLOWED\"\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "The design binds the type `EmployeeBadge` to this issuer. With `vctHostingMode: HOSTED` the tenant publishes its type metadata at `/public/schema/vct/EmployeeBadge`."
              },
              "response": []
            },
            {
              "name": "7. Get the EmployeeBadge configuration",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200 or 404', () => pm.expect(pm.response.code).to.be.oneOf([200, 404]));",
                      "if ([200, 404].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('employeeBadgeResourceId', pm.response.code === 200 ? pm.response.json().resourceId : '');",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/EmployeeBadge",
                "description": "Returns 404 until the configuration exists. When it exists, its `resourceId` is kept so the next request updates it."
              },
              "response": []
            },
            {
              "name": "8. Save the EmployeeBadge configuration",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (!pm.collectionVariables.get('employeeBadgeResourceId')) pm.collectionVariables.set('employeeBadgeResourceId', require('uuid').v4());"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  console.log('Saved. Waiting 6 seconds until the issuer picks up the new configuration.');",
                      "  setTimeout(() => {}, 6000);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/EmployeeBadge",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"resourceId\": \"{{employeeBadgeResourceId}}\",\n  \"credentialConfigurationId\": \"EmployeeBadge\",\n  \"format\": \"dc+sd-jwt\",\n  \"scope\": \"employee_badge\",\n  \"vct\": \"https://{{tenantSlug}}.{{baseDomain}}/public/schema/vct/EmployeeBadge\",\n  \"cryptographicBindingMethodsSupported\": [\n    \"jwk\"\n  ],\n  \"credentialSigningAlgValuesSupported\": [\n    \"ES256\"\n  ],\n  \"proofTypesSupported\": {\n    \"jwt\": {\n      \"proofSigningAlgValuesSupported\": [\n        \"ES256\"\n      ]\n    }\n  },\n  \"kmsResourceHandle\": \"{{signingKmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{signingKeyAlias}}\",\n  \"signingKeyMode\": \"did:web\",\n  \"signingVerificationMethodId\": \"{{signingVerificationMethodId}}\",\n  \"validityPeriod\": \"P365D\",\n  \"status\": {\n    \"statusListId\": \"employee-badge-status\",\n    \"purpose\": \"revocation\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Creates or updates the configuration. A new configuration needs a `resourceId`: the pre-request script generates one, or keeps the existing one."
              },
              "response": []
            },
            {
              "name": "9. Read the published type metadata",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/public/schema/vct/EmployeeBadge",
                "auth": {
                  "type": "noauth"
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Issue credentials",
          "description": "Create a credential offer, then receive it in a test wallet or scan `offer_uri` with a real wallet. Documentation: https://docs.sphereon.com/edk/deployment/enterprise-deployment/issuing",
          "item": [
            {
              "name": "1. List issuance templates",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const templates = pm.response.json();",
                      "  console.log('Templates: ' + templates.map(t => t.name + ' ' + t.id).join(', '));",
                      "  if (templates.length) pm.collectionVariables.set('issuanceTemplateId', templates[0].id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/templates/issuance",
                "description": "Templates hold ready-made offers. The sample tenant has one for EuPid."
              },
              "response": []
            },
            {
              "name": "2. Create an offer from a template",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  const offer = pm.response.json();",
                      "  pm.collectionVariables.set('offerCorrelationId', offer.correlation_id);",
                      "  pm.collectionVariables.set('credentialOfferUri', decodeURIComponent(offer.offer_uri.split('credential_offer_uri=')[1]));",
                      "  if (offer.tx_code) pm.collectionVariables.set('txCode', offer.tx_code);",
                      "  console.log('Wallet link: ' + offer.offer_uri);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/templates/issuance/{{issuanceTemplateId}}/credential-offers",
                "body": {
                  "mode": "raw",
                  "raw": "{}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "3. Create an EuPid offer",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  const offer = pm.response.json();",
                      "  pm.collectionVariables.set('offerCorrelationId', offer.correlation_id);",
                      "  pm.collectionVariables.set('credentialOfferUri', decodeURIComponent(offer.offer_uri.split('credential_offer_uri=')[1]));",
                      "  if (offer.tx_code) pm.collectionVariables.set('txCode', offer.tx_code);",
                      "  console.log('Wallet link: ' + offer.offer_uri);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/backend/credential/offers",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"credential_configuration_ids\": [\n    \"EuPid\"\n  ],\n  \"grants\": {\n    \"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\": \"T22000129\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "An offer with the pre-authorized code grant: the wallet needs no login. `credential_subject_data` holds the claims."
              },
              "response": []
            },
            {
              "name": "4. Create an EmployeeBadge offer with a transaction code",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  const offer = pm.response.json();",
                      "  pm.collectionVariables.set('offerCorrelationId', offer.correlation_id);",
                      "  pm.collectionVariables.set('credentialOfferUri', decodeURIComponent(offer.offer_uri.split('credential_offer_uri=')[1]));",
                      "  if (offer.tx_code) pm.collectionVariables.set('txCode', offer.tx_code);",
                      "  console.log('Wallet link: ' + offer.offer_uri);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/backend/credential/offers",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"credential_configuration_ids\": [\n    \"EmployeeBadge\"\n  ],\n  \"grants\": {\n    \"pre_authorized_code\": {\n      \"tx_code\": {\n        \"input_mode\": \"numeric\",\n        \"length\": 6\n      }\n    }\n  },\n  \"credential_subject_data\": {\n    \"given_name\": \"Ada\",\n    \"family_name\": \"Lovelace\",\n    \"employee_id\": \"E-1001\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Requires **Define your own credential**. The wallet must also enter the transaction code, which you deliver to the holder on another channel. The response returns it once."
              },
              "response": []
            },
            {
              "name": "5. Get the offer status",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  console.log('Offer status ' + (pm.response.json().status || JSON.stringify(pm.response.json()).slice(0, 120)));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/backend/credential/offers/{{offerCorrelationId}}",
                "description": "Shows where the wallet is: offer created, token issued, credential issued."
              },
              "response": []
            },
            {
              "name": "Receive it in a test wallet",
              "description": "Plays the wallet side of the last offer: resolve it, redeem the pre-authorized code and request the credential with a holder proof. Real wallets do the same after scanning `offer_uri`.",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Test wallet: a P-256 key pair generated in the Postman sandbox signs the holder proof JWT.",
                      "const cryptoJs = require('crypto-js');",
                      "// The private scalar stays in a collection variable and is only used inside this script.",
                      "const P = BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff');",
                      "const N = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');",
                      "const G = {x: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'), y: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5')};",
                      "const mod = (v, m) => { const r = v % m; return r >= 0n ? r : r + m; };",
                      "const inv = (v, m) => { let [a, b, x, y] = [mod(v, m), m, 1n, 0n]; while (a > 1n) { const q = b / a; [a, b] = [b - q * a, a]; [x, y] = [y - q * x, x]; } return mod(x, m); };",
                      "const add = (p, q) => {",
                      "  if (!p) return q; if (!q) return p;",
                      "  if (p.x === q.x && mod(p.y + q.y, P) === 0n) return null;",
                      "  const s = p.x === q.x ? mod((3n * p.x * p.x - 3n) * inv(2n * p.y, P), P) : mod((q.y - p.y) * inv(q.x - p.x, P), P);",
                      "  const x = mod(s * s - p.x - q.x, P);",
                      "  return {x, y: mod(s * (p.x - x) - p.y, P)};",
                      "};",
                      "const mul = (k, p) => { let r = null; for (let a = p; k > 0n; k >>= 1n, a = add(a, a)) if (k & 1n) r = add(r, a); return r; };",
                      "const rnd = () => { for (;;) { const k = BigInt('0x' + cryptoJs.lib.WordArray.random(32).toString()); if (k > 0n && k < N) return k; } };",
                      "const hex = v => v.toString(16).padStart(64, '0');",
                      "const b64u = words => cryptoJs.enc.Base64.stringify(words).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                      "const b64uJson = v => b64u(cryptoJs.enc.Utf8.parse(JSON.stringify(v)));",
                      "let secret = pm.collectionVariables.get('holderPrivateKey');",
                      "if (!/^[0-9a-f]{64}$/.test(secret || '')) {",
                      "  const d = rnd(); const q = mul(d, G);",
                      "  secret = hex(d);",
                      "  pm.collectionVariables.set('holderPrivateKey', secret);",
                      "  pm.collectionVariables.set('holderPublicJwk', JSON.stringify({kty: 'EC', crv: 'P-256', x: b64u(cryptoJs.enc.Hex.parse(hex(q.x))), y: b64u(cryptoJs.enc.Hex.parse(hex(q.y)))}));",
                      "}",
                      "const sign = input => {",
                      "  const z = BigInt('0x' + cryptoJs.SHA256(input).toString());",
                      "  for (;;) {",
                      "    const k = rnd(); const r = mod(mul(k, G).x, N); if (r === 0n) continue;",
                      "    let s = mod(inv(k, N) * (z + r * BigInt('0x' + secret)), N); if (s === 0n) continue;",
                      "    if (s > N / 2n) s = N - s;",
                      "    return b64u(cryptoJs.enc.Hex.parse(hex(r) + hex(s)));",
                      "  }",
                      "};",
                      "const buildProof = () => {",
                      "  const header = {alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: JSON.parse(pm.collectionVariables.get('holderPublicJwk'))};",
                      "  const payload = {aud: pm.collectionVariables.get('credentialIssuer'), iat: Math.floor(Date.now() / 1000), nonce: pm.collectionVariables.get('cNonce')};",
                      "  const input = b64uJson(header) + '.' + b64uJson(payload);",
                      "  pm.collectionVariables.set('proofJwt', input + '.' + sign(input));",
                      "};",
                      "// Builds the proof for the credential request that is about to be sent.",
                      "if (pm.variables.replaceIn(pm.request.url.toString()) === pm.collectionVariables.get('credentialEndpoint')) {",
                      "  buildProof();",
                      "}"
                    ]
                  }
                }
              ],
              "item": [
                {
                  "name": "1. Resolve the credential offer",
                  "event": [
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const offer = pm.response.json();",
                          "  const grant = offer.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'];",
                          "  pm.collectionVariables.set('credentialIssuer', offer.credential_issuer);",
                          "  pm.collectionVariables.set('preAuthorizedCode', grant ? grant['pre-authorized_code'] : '');",
                          "  pm.collectionVariables.set('txCodeRequired', String(Boolean(grant && grant.tx_code)));",
                          "  const [, origin, issuerPath] = offer.credential_issuer.match(/^(https?:\\/\\/[^/]+)(.*)$/);",
                          "  pm.collectionVariables.set('issuerMetadataUrl', origin + '/.well-known/openid-credential-issuer' + issuerPath.replace(/\\/$/, ''));",
                          "  console.log('Offer for ' + offer.credential_configuration_ids.join(', ') + (grant && grant.tx_code ? ' (needs a transaction code)' : ''));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "{{credentialOfferUri}}",
                    "auth": {
                      "type": "noauth"
                    }
                  },
                  "response": []
                },
                {
                  "name": "2. Read issuer metadata",
                  "event": [
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const metadata = pm.response.json();",
                          "  pm.collectionVariables.set('credentialEndpoint', metadata.credential_endpoint);",
                          "  pm.collectionVariables.set('nonceEndpoint', metadata.nonce_endpoint);",
                          "  pm.collectionVariables.set('walletAuthorizationServer', (metadata.authorization_servers || [metadata.credential_issuer])[0]);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "{{issuerMetadataUrl}}",
                    "auth": {
                      "type": "noauth"
                    }
                  },
                  "response": []
                },
                {
                  "name": "3. Read authorization server metadata",
                  "event": [
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('walletTokenEndpoint', pm.response.json().token_endpoint);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "{{walletAuthorizationServer}}/.well-known/openid-configuration",
                    "auth": {
                      "type": "noauth"
                    }
                  },
                  "response": []
                },
                {
                  "name": "4. Redeem the pre-authorized code",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "if (pm.collectionVariables.get('txCodeRequired') !== 'true') pm.request.body.urlencoded.remove('tx_code');"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const token = pm.response.json();",
                          "  pm.collectionVariables.set('walletAccessToken', token.access_token);",
                          "  const detail = (token.authorization_details || []).find(d => d.type === 'openid_credential');",
                          "  pm.collectionVariables.set('credentialIdentifier', detail ? detail.credential_identifiers[0] : '');",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [],
                    "url": "{{walletTokenEndpoint}}",
                    "auth": {
                      "type": "noauth"
                    },
                    "body": {
                      "mode": "urlencoded",
                      "urlencoded": [
                        {
                          "key": "grant_type",
                          "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code",
                          "type": "text"
                        },
                        {
                          "key": "pre-authorized_code",
                          "value": "{{preAuthorizedCode}}",
                          "type": "text"
                        },
                        {
                          "key": "tx_code",
                          "value": "{{txCode}}",
                          "type": "text"
                        }
                      ]
                    },
                    "description": "The wallet exchanges the pre-authorized code for an access token. When the offer asks for a transaction code, the holder types in the code you gave them; here it is taken from the offer response (`txCode`)."
                  },
                  "response": []
                },
                {
                  "name": "5. Get a nonce",
                  "event": [
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('cNonce', pm.response.json().c_nonce);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [],
                    "url": "{{nonceEndpoint}}",
                    "auth": {
                      "type": "noauth"
                    }
                  },
                  "response": []
                },
                {
                  "name": "6. Request the credential",
                  "event": [
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const cryptoJs = require('crypto-js');",
                          "  const credential = pm.response.json().credentials[0].credential;",
                          "  console.log('Issued credential: ' + String(credential).slice(0, 80) + '...');",
                          "  if (typeof credential === 'string' && credential.includes('.')) {",
                          "    const part = credential.split('~')[0].split('.')[1].replace(/-/g, '+').replace(/_/g, '/');",
                          "    const claims = JSON.parse(cryptoJs.enc.Base64.parse(part + '='.repeat((4 - part.length % 4) % 4)).toString(cryptoJs.enc.Utf8));",
                          "    const entry = claims.status && claims.status.status_list;",
                          "    if (entry) {",
                          "      pm.collectionVariables.set('credentialStatusIndex', String(entry.idx));",
                          "      pm.collectionVariables.set('credentialStatusListUri', entry.uri);",
                          "      console.log('Status entry ' + entry.idx + ' in ' + entry.uri);",
                          "    }",
                          "  }",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "{{credentialEndpoint}}",
                    "auth": {
                      "type": "bearer",
                      "bearer": [
                        {
                          "key": "token",
                          "value": "{{walletAccessToken}}",
                          "type": "string"
                        }
                      ]
                    },
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"credential_identifier\": \"{{credentialIdentifier}}\",\n  \"proofs\": {\n    \"jwt\": [\n      \"{{proofJwt}}\"\n    ]\n  }\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "The folder script signs a holder proof over the nonce with the test wallet key. The response holds the issued credential. For an SD-JWT VC the status list entry is stored so you can revoke it."
                  },
                  "response": []
                }
              ]
            },
            {
              "name": "6. Find the status list of the credential",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (!pm.collectionVariables.get('credentialStatusListUri')) {",
                      "  console.log('Skipped: receive an SD-JWT VC in the test wallet first.');",
                      "  pm.execution.skipRequest();",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const correlationId = pm.collectionVariables.get('credentialStatusListUri').split('/').pop();",
                      "  const list = (pm.response.json().data || []).find(l => l.correlationId === correlationId);",
                      "  pm.test('status list found', () => pm.expect(list, correlationId).to.be.an('object'));",
                      "  pm.collectionVariables.set('credentialStatusListId', list.id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/statuslist/v1/statuslists?limit=100",
                "description": "The credential names its status list by URL. The last URL segment is the list's `correlationId`; the status API addresses the list by its `id`."
              },
              "response": []
            },
            {
              "name": "7. Revoke the issued credential",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (!pm.collectionVariables.get('credentialStatusListId')) pm.execution.skipRequest();"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/statuslist/v1/statuslists/{{credentialStatusListId}}/status",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"statusListIndex\": {{credentialStatusIndex}},\n  \"value\": 1\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Sets the status entry of the credential you just received to 1 (revoked). Verifiers see it once they fetch the list again."
              },
              "response": []
            },
            {
              "name": "8. Read the status entry",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (!pm.collectionVariables.get('credentialStatusListId')) pm.execution.skipRequest();"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  console.log(JSON.stringify(pm.response.json()));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/statuslist/v1/statuslists/{{credentialStatusListId}}/entries/{{credentialStatusIndex}}"
              },
              "response": []
            }
          ]
        },
        {
          "name": "Verify credentials",
          "description": "Ask a wallet to present credentials with OID4VP. What to ask for is a DCQL query; a verification template combines a query with request defaults. Documentation: https://docs.sphereon.com/edk/deployment/enterprise-deployment/dcql-queries",
          "item": [
            {
              "name": "1. List verifiers",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  pm.collectionVariables.set('verifierId', pm.response.json().items[0].id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vp/v1/instances"
              },
              "response": []
            },
            {
              "name": "2. Get the verifier client settings",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vp/verifier/client"
              },
              "response": []
            },
            {
              "name": "3. List DCQL queries",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const queries = pm.response.json();",
                      "  console.log('Queries: ' + queries.map(q => q.queryId).join(', '));",
                      "  pm.collectionVariables.set('employeeBadgeQueryExists', String(queries.some(q => q.queryId === 'employee-badge')));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/dcql/v1/queries"
              },
              "response": []
            },
            {
              "name": "4. Create a DCQL query",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.collectionVariables.get('employeeBadgeQueryExists') === 'true') {",
                      "  console.log('Skipped: the employee-badge query exists.');",
                      "  pm.execution.skipRequest();",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/dcql/v1/queries",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"queryId\": \"employee-badge\",\n  \"name\": \"Employee badge\",\n  \"description\": \"Name and employee number from the employee badge\",\n  \"enabled\": true,\n  \"dcqlQuery\": {\n    \"credentials\": [\n      {\n        \"id\": \"badge\",\n        \"format\": \"dc+sd-jwt\",\n        \"meta\": {\n          \"vct_values\": [\n            \"https://{{tenantSlug}}.{{baseDomain}}/public/schema/vct/EmployeeBadge\"\n          ]\n        },\n        \"claims\": [\n          {\n            \"path\": [\n              \"given_name\"\n            ]\n          },\n          {\n            \"path\": [\n              \"family_name\"\n            ]\n          },\n          {\n            \"path\": [\n              \"employee_id\"\n            ]\n          }\n        ]\n      }\n    ]\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "Asks for an EmployeeBadge with the holder's name and employee number. See **Define your own credential**."
              },
              "response": []
            },
            {
              "name": "5. List query bindings of the verifier",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const bindings = pm.response.json();",
                      "  pm.collectionVariables.set('employeeBadgeQueryBound', String((Array.isArray(bindings) ? bindings : bindings.items || []).some(b => b.queryId === 'employee-badge')));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/dcql/v1/verifiers/{{verifierId}}/bindings"
              },
              "response": []
            },
            {
              "name": "6. Bind the query to the verifier",
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (pm.collectionVariables.get('employeeBadgeQueryBound') === 'true') {",
                      "  console.log('Skipped: employee-badge is already bound.');",
                      "  pm.execution.skipRequest();",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/dcql/v1/verifiers/{{verifierId}}/bindings",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"queryId\": \"employee-badge\",\n  \"version\": 1\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "7. List verification templates",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const templates = pm.response.json();",
                      "  console.log('Templates: ' + templates.map(t => t.name + ' ' + t.id).join(', '));",
                      "  if (templates.length) pm.collectionVariables.set('verificationTemplateId', templates[0].id);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vp/v1/templates/verification"
              },
              "response": []
            },
            {
              "name": "8. Create a request from a template",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                      "if ([201].includes(pm.response.code)) {",
                      "  const result = pm.response.json();",
                      "  pm.collectionVariables.set('verificationCorrelationId', result.correlation_id);",
                      "  pm.collectionVariables.set('requestObjectUri', decodeURIComponent(result.request_uri.split('request_uri=')[1]));",
                      "  console.log('Wallet link: ' + result.request_uri);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vp/v1/templates/verification/{{verificationTemplateId}}/authorization-requests",
                "body": {
                  "mode": "raw",
                  "raw": "{}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "9. Create a request for a query",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200 or 201', () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));",
                      "if ([200, 201].includes(pm.response.code)) {",
                      "  const result = pm.response.json();",
                      "  pm.collectionVariables.set('verificationCorrelationId', result.correlation_id);",
                      "  pm.collectionVariables.set('requestObjectUri', decodeURIComponent(result.request_uri.split('request_uri=')[1]));",
                      "  console.log('Wallet link: ' + result.request_uri);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/oid4vp/backend/auth/requests",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"query_id\": \"employee-badge\",\n  \"verifier_id\": \"{{verifierId}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": "The same without a template: name the query and the verifier."
              },
              "response": []
            },
            {
              "name": "10. Fetch the signed request object",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "{{requestObjectUri}}",
                "auth": {
                  "type": "noauth"
                },
                "description": "What the wallet downloads: a signed JWT with the DCQL query and where to post the response."
              },
              "response": []
            },
            {
              "name": "11. Get the verification status",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  console.log('Status ' + pm.response.json().status);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/oid4vp/backend/auth/requests/{{verificationCorrelationId}}",
                "description": "Poll until the wallet has responded. The verified claims are in the response."
              },
              "response": []
            }
          ]
        },
        {
          "name": "Trust domains",
          "description": "Which issuers the verifier trusts. A trust domain holds anchors (issuer keys or certificates); an attachment links domains to a consumer such as the tenant's verifier.",
          "item": [
            {
              "name": "1. List trust domains",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const domain = (pm.response.json().items || [])[0];",
                      "  if (domain) pm.collectionVariables.set('trustDomainId', domain.domainId);",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/trust-domain/v1/domains"
              },
              "response": []
            },
            {
              "name": "2. Get a trust domain",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/trust-domain/v1/domains/{{trustDomainId}}"
              },
              "response": []
            },
            {
              "name": "3. List its anchors",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/trust-domain/v1/domains/{{trustDomainId}}/anchors"
              },
              "response": []
            },
            {
              "name": "4. Get the tenant issuer-trust attachment",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                      "if ([200].includes(pm.response.code)) {",
                      "  const {attachment, domains} = pm.response.json();",
                      "  pm.test('issuers are admitted by identity and unknown issuers are refused', () => {",
                      "    pm.expect(attachment.policy.type).to.eql('IDENTITY_ADMISSION');",
                      "    pm.expect(attachment.policy.mode).to.eql('FAIL_CLOSED');",
                      "  });",
                      "  console.log('Trusted issuer domains: ' + domains.map(d => d.domainId).join(', '));",
                      "}"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/trust-domain/v1/attachments/TENANT/{{tenantId}}/CREDENTIAL_ISSUER_TRUST",
                "description": "The trust domains the tenant's verifiers consult for credential issuers. With the `IDENTITY_ADMISSION` policy in `FAIL_CLOSED` mode, a credential from an issuer that no attached domain admits is rejected."
              },
              "response": []
            },
            {
              "name": "5. Get the verifier eligibility",
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                    ]
                  }
                }
              ],
              "request": {
                "method": "GET",
                "header": [],
                "url": "https://{{tenantSlug}}.{{baseDomain}}/api/trust-domain/v1/eligibility/OID4VP_VERIFIER/CREDENTIAL_ISSUER_TRUST"
              },
              "response": []
            }
          ]
        },
        {
          "name": "Keycloak wallet login (optional)",
          "description": "Let wallet users sign in with Keycloak (or another OpenID provider) before they receive a credential, with the OID4VCI authorization code flow.",
          "item": [
            {
              "name": "Set up the wallet proxy",
              "description": "A second authorization server, the **wallet proxy**, is hosted at `/as/<walletProxySlug>` and sends wallet users to Keycloak to sign in. The tenant's default authorization server is not changed, and no credential uses the proxy until you choose so in the next folder.\n\nIn Keycloak, create a confidential OpenID Connect client with the standard flow and this redirect URI: `https://<tenantSlug>.<baseDomain>/as/wallet-proxy/federation/callback`. Set `keycloakIssuer` (for example `https://idp.example.com/realms/acme`), `keycloakClientId` and `keycloakClientSecret`. Keycloak needs no OID4VCI support. Each step checks what exists first, so you can run the folder again. Documentation: https://docs.sphereon.com/edk/guides/tenant/federation",
              "item": [
                {
                  "name": "1. List authorization servers",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const servers = pm.response.json().items || [];",
                          "  const proxy = servers.find(s => s.slug === pm.variables.replaceIn('{{walletProxySlug}}'));",
                          "  const keycloak = servers.find(s => s.deployment === 'EXTERNAL' && s.issuer === pm.variables.replaceIn('{{keycloakIssuer}}'));",
                          "  pm.collectionVariables.set('walletProxyId', proxy ? proxy.id : '');",
                          "  pm.collectionVariables.set('keycloakServerId', keycloak ? keycloak.id : '');",
                          "  pm.collectionVariables.set('keycloakServerRevision', keycloak ? String(keycloak.revision) : '');",
                          "  pm.collectionVariables.set('keycloakServerLifecycle', keycloak ? keycloak.lifecycle : '');",
                          "  pm.collectionVariables.set('federationBindingId', '');",
                          "  console.log('Wallet proxy: ' + (proxy ? proxy.id : 'not yet') + ', Keycloak: ' + (keycloak ? keycloak.id + ' ' + keycloak.lifecycle : 'not yet'));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers?page=0&size=100"
                  },
                  "response": []
                },
                {
                  "name": "2. Create the wallet proxy",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped && pm.collectionVariables.get('walletProxyId')) { console.log('Skipped: the wallet proxy exists.'); pm.execution.skipRequest(); }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                          "if ([201].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('walletProxyId', pm.response.json().id);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"slug\": \"{{walletProxySlug}}\",\n  \"displayName\": \"{{tenantName}} wallet login\",\n  \"issuer\": \"https://{{tenantSlug}}.{{baseDomain}}/as/{{walletProxySlug}}\",\n  \"deployment\": \"HOSTED\",\n  \"authenticationMode\": \"FEDERATED_ONLY\",\n  \"purposes\": [\n    \"CREDENTIAL_ISSUANCE\",\n    \"WALLET_LOGIN\"\n  ],\n  \"usages\": [],\n  \"allowedGrantTypes\": [\n    \"authorization_code\",\n    \"refresh_token\"\n  ]\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    }
                  },
                  "response": []
                },
                {
                  "name": "3. List wallet proxy clients",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"walletProxyId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Create the wallet proxy first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('walletClientExists', String((pm.response.json().clients || []).some(c => c.clientId === pm.variables.replaceIn('{{walletClientId}}'))));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{walletProxyId}}/clients"
                  },
                  "response": []
                },
                {
                  "name": "4. Register the wallet client",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"walletProxyId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Create the wallet proxy first.'); pm.execution.skipRequest(); skipped = true; break; }",
                          "if (!skipped && pm.collectionVariables.get('walletClientExists') === 'true') { console.log('Skipped: the wallet client exists.'); pm.execution.skipRequest(); }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{walletProxyId}}/clients",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"clientId\": \"{{walletClientId}}\",\n  \"clientName\": \"Postman wallet\",\n  \"clientType\": \"public\",\n  \"enabled\": true,\n  \"grantTypes\": [\n    \"authorization_code\",\n    \"refresh_token\"\n  ],\n  \"responseTypes\": [\n    \"code\"\n  ],\n  \"redirectUris\": [\n    \"https://oauth.pstmn.io/v1/browser-callback\"\n  ],\n  \"allowedScopes\": [\n    \"openid\",\n    \"profile\",\n    \"email\"\n  ],\n  \"tokenEndpointAuthMethod\": \"none\",\n  \"requirePkce\": true\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "A public client with PKCE, used by the Postman wallet in the next folder. Register your real wallet the same way with its own redirect URI."
                  },
                  "response": []
                },
                {
                  "name": "5. Register Keycloak",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped && pm.collectionVariables.get('keycloakServerId')) { console.log('Skipped: Keycloak is registered.'); pm.execution.skipRequest(); }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                          "if ([201].includes(pm.response.code)) {",
                          "  const server = pm.response.json();",
                          "  pm.collectionVariables.set('keycloakServerId', server.id);",
                          "  pm.collectionVariables.set('keycloakServerRevision', String(server.revision));",
                          "  pm.collectionVariables.set('keycloakServerLifecycle', server.lifecycle);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"displayName\": \"Keycloak\",\n  \"issuer\": \"{{keycloakIssuer}}\",\n  \"deployment\": \"EXTERNAL\",\n  \"expectedCapabilities\": [\n    \"OAUTH2\",\n    \"OIDC\"\n  ],\n  \"purposes\": [\n    \"WALLET_LOGIN\"\n  ],\n  \"usages\": [\n    \"HOSTED_LOGIN_UPSTREAM\"\n  ],\n  \"allowedGrantTypes\": [\n    \"authorization_code\"\n  ]\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "Registers the Keycloak realm as an external authorization server. The platform reads its discovery document, so Keycloak must be reachable from the deployment."
                  },
                  "response": []
                },
                {
                  "name": "6. Validate Keycloak discovery",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"keycloakServerId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Register Keycloak first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.test('discovery is valid', () => pm.expect(pm.response.json().valid).to.eql(true));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{keycloakServerId}}/validate"
                  },
                  "response": []
                },
                {
                  "name": "7. Activate Keycloak",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"keycloakServerId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Register Keycloak first.'); pm.execution.skipRequest(); skipped = true; break; }",
                          "if (!skipped && pm.collectionVariables.get('keycloakServerLifecycle') === 'ACTIVE') { console.log('Skipped: Keycloak is active.'); pm.execution.skipRequest(); }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{keycloakServerId}}/activate",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"expectedRevision\": {{keycloakServerRevision}}\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    }
                  },
                  "response": []
                },
                {
                  "name": "8. List federation bindings",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"walletProxyId\",\"keycloakServerId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Register Keycloak first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const binding = (pm.response.json().items || []).find(b => b.externalAuthorizationServerId === pm.collectionVariables.get('keycloakServerId'));",
                          "  pm.collectionVariables.set('federationBindingId', binding ? binding.id : '');",
                          "  pm.collectionVariables.set('federationBindingEnabled', String(Boolean(binding && binding.enabled)));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{walletProxyId}}/federation-bindings"
                  },
                  "response": []
                },
                {
                  "name": "9. Create the federation binding",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"walletProxyId\",\"keycloakServerId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Register Keycloak first.'); pm.execution.skipRequest(); skipped = true; break; }",
                          "if (!skipped && pm.collectionVariables.get('federationBindingId')) { console.log('Skipped: the binding exists.'); pm.execution.skipRequest(); }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                          "if ([201].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('federationBindingId', pm.response.json().id);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{walletProxyId}}/federation-bindings",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"externalAuthorizationServerId\": \"{{keycloakServerId}}\",\n  \"order\": 0,\n  \"enabled\": false,\n  \"scopes\": [\n    \"openid\",\n    \"profile\",\n    \"email\"\n  ],\n  \"claimsMapping\": {\n    \"subject\": \"sub\",\n    \"email\": \"email\",\n    \"displayName\": \"name\"\n  },\n  \"clientAuthentication\": {\n    \"method\": \"client_secret_basic\",\n    \"clientId\": \"{{keycloakClientId}}\",\n    \"secretValue\": \"{{keycloakClientSecret}}\"\n  }\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "Connects the wallet proxy to Keycloak with the Keycloak client. The binding starts disabled. The client secret is stored and never returned."
                  },
                  "response": []
                },
                {
                  "name": "10. Validate the federation binding",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"federationBindingId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Create the federation binding first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('federationBindingEnabled', String(pm.response.json().enabled === true));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{walletProxyId}}/federation-bindings/{{federationBindingId}}/validate",
                    "description": "Checks the binding against Keycloak. When the binding is not enabled afterwards, the next request enables it."
                  },
                  "response": []
                },
                {
                  "name": "11. Enable the federation binding",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"federationBindingId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Create the federation binding first.'); pm.execution.skipRequest(); skipped = true; break; }",
                          "if (!skipped && pm.collectionVariables.get('federationBindingEnabled') === 'true') { console.log('Skipped: the binding is enabled.'); pm.execution.skipRequest(); }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('federationBindingEnabled', 'true');",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/authorization-servers/{{walletProxyId}}/federation-bindings/{{federationBindingId}}/enable"
                  },
                  "response": []
                },
                {
                  "name": "12. List credential issuers",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const issuer = (pm.response.json().items || [])[0];",
                          "  pm.collectionVariables.set('issuerId', issuer.id);",
                          "  pm.collectionVariables.set('credentialIssuer', issuer.credentialIssuer);",
                          "  console.log('Issuer ' + issuer.id + ' at ' + issuer.credentialIssuer);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/instances"
                  },
                  "response": []
                },
                {
                  "name": "13. List the issuer's authorization servers",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"issuerId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run List credential issuers first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const bound = (pm.response.json().items || []).find(b => b.authorizationServerId === pm.collectionVariables.get('walletProxyId'));",
                          "  pm.collectionVariables.set('issuerBindingId', bound ? bound.id : '');",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/authorization-server-bindings"
                  },
                  "response": []
                },
                {
                  "name": "14. Allow the issuer to use the wallet proxy",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"walletProxyId\",\"issuerId\",\"federationBindingId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Create the federation binding first.'); pm.execution.skipRequest(); skipped = true; break; }",
                          "if (!skipped && pm.collectionVariables.get('issuerBindingId')) { console.log('Skipped: the issuer may use the wallet proxy.'); pm.execution.skipRequest(); }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                          "if ([201].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('issuerBindingId', pm.response.json().id);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/authorization-server-bindings",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"authorizationServerId\": \"{{walletProxyId}}\",\n  \"enabled\": true,\n  \"default\": false,\n  \"allowedGrantTypes\": [\n    \"authorization_code\"\n  ]\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "The issuer may now send wallets to the wallet proxy for the authorization code grant. The tenant's default authorization server stays the default."
                  },
                  "response": []
                }
              ]
            },
            {
              "name": "Issue EuPid with a Keycloak login",
              "description": "The wallet signs in at Keycloak before it receives the credential. Run **Set up the wallet proxy** first.\n\nWhile EuPid uses the wallet proxy it only allows the authorization code grant, so the pre-authorized EuPid offers in **Issue credentials** are refused. The last request switches EuPid back.",
              "item": [
                {
                  "name": "1. Get the EuPid authorization server choice",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"issuerId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Set up the wallet proxy first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('euPidAuthorizationServerRevision', String(pm.response.json().revision));",
                          "  console.log('EuPid authorization server: ' + (pm.response.json().authorizationServerId || 'tenant default'));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/credential-configurations/EuPid/authorization-server"
                  },
                  "response": []
                },
                {
                  "name": "2. Use the wallet proxy for EuPid",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"walletProxyId\",\"issuerBindingId\",\"euPidAuthorizationServerRevision\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Set up the wallet proxy first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('euPidAuthorizationServerRevision', String(pm.response.json().revision));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "PUT",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/credential-configurations/EuPid/authorization-server",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"authorizationServerId\": \"{{walletProxyId}}\",\n  \"allowedGrantTypes\": [\"authorization_code\"],\n  \"expectedRevision\": {{euPidAuthorizationServerRevision}}\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "EuPid offers now send the wallet to the wallet proxy, which sends the user to Keycloak. Other credential configurations keep their authorization server."
                  },
                  "response": []
                },
                {
                  "name": "3. Create an authorization code offer",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 201', () => pm.response.to.have.status(201));",
                          "if ([201].includes(pm.response.code)) {",
                          "  const offer = pm.response.json();",
                          "  pm.collectionVariables.set('offerCorrelationId', offer.correlation_id);",
                          "  pm.collectionVariables.set('credentialOfferUri', decodeURIComponent(offer.offer_uri.split('credential_offer_uri=')[1]));",
                          "  console.log('Wallet link: ' + offer.offer_uri);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "POST",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/oid4vci/v1/backend/credential/offers",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"credential_configuration_ids\": [\n    \"EuPid\"\n  ],\n  \"grants\": {\n    \"authorization_code\": {\n      \"issuer_state\": \"postman-{{$guid}}\",\n      \"authorization_server\": \"https://{{tenantSlug}}.{{baseDomain}}/as/{{walletProxySlug}}\"\n    }\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\": \"T22000129\"\n  }\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    }
                  },
                  "response": []
                },
                {
                  "name": "4. Resolve the offer",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  const offer = pm.response.json();",
                          "  pm.collectionVariables.set('issuerState', offer.grants.authorization_code.issuer_state);",
                          "  pm.collectionVariables.set('credentialIssuer', offer.credential_issuer);",
                          "  const [, origin, issuerPath] = offer.credential_issuer.match(/^(https?:\\/\\/[^/]+)(.*)$/);",
                          "  pm.collectionVariables.set('issuerMetadataUrl', origin + '/.well-known/openid-credential-issuer' + issuerPath.replace(/\\/$/, ''));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "{{credentialOfferUri}}",
                    "auth": {
                      "type": "noauth"
                    }
                  },
                  "response": []
                },
                {
                  "name": "5. Read issuer metadata",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('credentialEndpoint', pm.response.json().credential_endpoint);",
                          "  pm.collectionVariables.set('nonceEndpoint', pm.response.json().nonce_endpoint);",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "{{issuerMetadataUrl}}",
                    "auth": {
                      "type": "noauth"
                    }
                  },
                  "response": []
                },
                {
                  "name": "6. Wallet: request the credential",
                  "description": "Open this folder's **Authorization** tab and choose **Get New Access Token**. The authorization request carries `issuer_state` and `authorization_details`; sign in at Keycloak, then choose **Use Token**. Postman shows the token response: copy `authorization_details[0].credential_identifiers[0]` into `walletCredentialIdentifier` in your environment and send the request. The folder script fetches a fresh nonce and signs the holder proof.",
                  "auth": {
                    "type": "oauth2",
                    "oauth2": [
                      {
                        "key": "tokenName",
                        "value": "Wallet through Keycloak",
                        "type": "string"
                      },
                      {
                        "key": "grant_type",
                        "value": "authorization_code_with_pkce",
                        "type": "string"
                      },
                      {
                        "key": "authUrl",
                        "value": "https://{{tenantSlug}}.{{baseDomain}}/as/{{walletProxySlug}}/authorize",
                        "type": "string"
                      },
                      {
                        "key": "accessTokenUrl",
                        "value": "https://{{tenantSlug}}.{{baseDomain}}/as/{{walletProxySlug}}/token",
                        "type": "string"
                      },
                      {
                        "key": "clientId",
                        "value": "{{walletClientId}}",
                        "type": "string"
                      },
                      {
                        "key": "redirect_uri",
                        "value": "https://oauth.pstmn.io/v1/browser-callback",
                        "type": "string"
                      },
                      {
                        "key": "useBrowser",
                        "value": false,
                        "type": "boolean"
                      },
                      {
                        "key": "scope",
                        "value": "openid profile email",
                        "type": "string"
                      },
                      {
                        "key": "challengeAlgorithm",
                        "value": "S256",
                        "type": "string"
                      },
                      {
                        "key": "state",
                        "value": "{{$guid}}",
                        "type": "string"
                      },
                      {
                        "key": "client_authentication",
                        "value": "body",
                        "type": "string"
                      },
                      {
                        "key": "addTokenTo",
                        "value": "header",
                        "type": "string"
                      },
                      {
                        "key": "headerPrefix",
                        "value": "Bearer",
                        "type": "string"
                      },
                      {
                        "key": "authRequestParams",
                        "value": [
                          {
                            "key": "issuer_state",
                            "value": "{{issuerState}}",
                            "enabled": true,
                            "send_as": "request_url"
                          },
                          {
                            "key": "authorization_details",
                            "value": "[{\"type\":\"openid_credential\",\"credential_configuration_id\":\"EuPid\"}]",
                            "enabled": true,
                            "send_as": "request_url"
                          }
                        ],
                        "type": "any"
                      }
                    ]
                  },
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "// Test wallet: a P-256 key pair generated in the Postman sandbox signs the holder proof JWT.",
                          "const cryptoJs = require('crypto-js');",
                          "// The private scalar stays in a collection variable and is only used inside this script.",
                          "const P = BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff');",
                          "const N = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');",
                          "const G = {x: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'), y: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5')};",
                          "const mod = (v, m) => { const r = v % m; return r >= 0n ? r : r + m; };",
                          "const inv = (v, m) => { let [a, b, x, y] = [mod(v, m), m, 1n, 0n]; while (a > 1n) { const q = b / a; [a, b] = [b - q * a, a]; [x, y] = [y - q * x, x]; } return mod(x, m); };",
                          "const add = (p, q) => {",
                          "  if (!p) return q; if (!q) return p;",
                          "  if (p.x === q.x && mod(p.y + q.y, P) === 0n) return null;",
                          "  const s = p.x === q.x ? mod((3n * p.x * p.x - 3n) * inv(2n * p.y, P), P) : mod((q.y - p.y) * inv(q.x - p.x, P), P);",
                          "  const x = mod(s * s - p.x - q.x, P);",
                          "  return {x, y: mod(s * (p.x - x) - p.y, P)};",
                          "};",
                          "const mul = (k, p) => { let r = null; for (let a = p; k > 0n; k >>= 1n, a = add(a, a)) if (k & 1n) r = add(r, a); return r; };",
                          "const rnd = () => { for (;;) { const k = BigInt('0x' + cryptoJs.lib.WordArray.random(32).toString()); if (k > 0n && k < N) return k; } };",
                          "const hex = v => v.toString(16).padStart(64, '0');",
                          "const b64u = words => cryptoJs.enc.Base64.stringify(words).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                          "const b64uJson = v => b64u(cryptoJs.enc.Utf8.parse(JSON.stringify(v)));",
                          "let secret = pm.collectionVariables.get('holderPrivateKey');",
                          "if (!/^[0-9a-f]{64}$/.test(secret || '')) {",
                          "  const d = rnd(); const q = mul(d, G);",
                          "  secret = hex(d);",
                          "  pm.collectionVariables.set('holderPrivateKey', secret);",
                          "  pm.collectionVariables.set('holderPublicJwk', JSON.stringify({kty: 'EC', crv: 'P-256', x: b64u(cryptoJs.enc.Hex.parse(hex(q.x))), y: b64u(cryptoJs.enc.Hex.parse(hex(q.y)))}));",
                          "}",
                          "const sign = input => {",
                          "  const z = BigInt('0x' + cryptoJs.SHA256(input).toString());",
                          "  for (;;) {",
                          "    const k = rnd(); const r = mod(mul(k, G).x, N); if (r === 0n) continue;",
                          "    let s = mod(inv(k, N) * (z + r * BigInt('0x' + secret)), N); if (s === 0n) continue;",
                          "    if (s > N / 2n) s = N - s;",
                          "    return b64u(cryptoJs.enc.Hex.parse(hex(r) + hex(s)));",
                          "  }",
                          "};",
                          "const buildProof = () => {",
                          "  const header = {alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: JSON.parse(pm.collectionVariables.get('holderPublicJwk'))};",
                          "  const payload = {aud: pm.collectionVariables.get('credentialIssuer'), iat: Math.floor(Date.now() / 1000), nonce: pm.collectionVariables.get('cNonce')};",
                          "  const input = b64uJson(header) + '.' + b64uJson(payload);",
                          "  pm.collectionVariables.set('proofJwt', input + '.' + sign(input));",
                          "};",
                          "// Builds the proof for the credential request that is about to be sent.",
                          "if (pm.variables.replaceIn(pm.request.url.toString()) === pm.collectionVariables.get('credentialEndpoint')) {",
                          "  pm.sendRequest({url: pm.collectionVariables.get('nonceEndpoint'), method: 'POST'}, (error, response) => {",
                          "    if (error) throw error;",
                          "    pm.collectionVariables.set('cNonce', response.json().c_nonce);",
                          "    buildProof();",
                          "  });",
                          "}"
                        ]
                      }
                    }
                  ],
                  "item": [
                    {
                      "name": "1. Request the credential",
                      "event": [
                        {
                          "listen": "prerequest",
                          "script": {
                            "type": "text/javascript",
                            "exec": [
                              "let skipped = false;",
                              "for (const key of [\"walletCredentialIdentifier\"]) {",
                              "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                              "  if (!value || value.startsWith('{{')) {",
                              "    console.log('Skipped: set ' + key + ' in your environment to request the credential after signing in at Keycloak.');",
                              "    pm.execution.skipRequest();",
                              "    skipped = true;",
                              "    break;",
                              "  }",
                              "}"
                            ]
                          }
                        },
                        {
                          "listen": "test",
                          "script": {
                            "type": "text/javascript",
                            "exec": [
                              "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                              "if ([200].includes(pm.response.code)) {",
                              "  console.log('Issued credential: ' + String(pm.response.json().credentials[0].credential).slice(0, 80) + '...');",
                              "}"
                            ]
                          }
                        }
                      ],
                      "request": {
                        "method": "POST",
                        "header": [
                          {
                            "key": "Content-Type",
                            "value": "application/json"
                          }
                        ],
                        "url": "{{credentialEndpoint}}",
                        "body": {
                          "mode": "raw",
                          "raw": "{\n  \"credential_identifier\": \"{{walletCredentialIdentifier}}\",\n  \"proofs\": {\n    \"jwt\": [\n      \"{{proofJwt}}\"\n    ]\n  }\n}",
                          "options": {
                            "raw": {
                              "language": "json"
                            }
                          }
                        }
                      },
                      "response": []
                    }
                  ]
                },
                {
                  "name": "7. Get the EuPid authorization server choice",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"issuerId\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Set up the wallet proxy first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                          "if ([200].includes(pm.response.code)) {",
                          "  pm.collectionVariables.set('euPidAuthorizationServerRevision', String(pm.response.json().revision));",
                          "  console.log('EuPid authorization server: ' + (pm.response.json().authorizationServerId || 'tenant default'));",
                          "}"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "GET",
                    "header": [],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/credential-configurations/EuPid/authorization-server"
                  },
                  "response": []
                },
                {
                  "name": "8. Switch EuPid back to the default authorization server",
                  "event": [
                    {
                      "listen": "prerequest",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "let skipped = false;",
                          "for (const key of [\"keycloakIssuer\",\"keycloakClientId\",\"keycloakClientSecret\"]) {",
                          "  const value = String(pm.variables.replaceIn('{{' + key + '}}') || '').trim();",
                          "  if (!value || value.startsWith('{{')) {",
                          "    console.log('Skipped: set ' + key + ' in your environment to use Keycloak as the wallet login.');",
                          "    pm.execution.skipRequest();",
                          "    skipped = true;",
                          "    break;",
                          "  }",
                          "}",
                          "if (!skipped) for (const key of [\"issuerId\",\"euPidAuthorizationServerRevision\"]) if (!pm.collectionVariables.get(key)) { console.log('Skipped: run Get the EuPid authorization server choice first.'); pm.execution.skipRequest(); skipped = true; break; }"
                        ]
                      }
                    },
                    {
                      "listen": "test",
                      "script": {
                        "type": "text/javascript",
                        "exec": [
                          "pm.test('HTTP 204', () => pm.response.to.have.status(204));"
                        ]
                      }
                    }
                  ],
                  "request": {
                    "method": "DELETE",
                    "header": [
                      {
                        "key": "Content-Type",
                        "value": "application/json"
                      }
                    ],
                    "url": "https://{{tenantSlug}}.{{baseDomain}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/credential-configurations/EuPid/authorization-server",
                    "body": {
                      "mode": "raw",
                      "raw": "{\n  \"expectedRevision\": {{euPidAuthorizationServerRevision}}\n}",
                      "options": {
                        "raw": {
                          "language": "json"
                        }
                      }
                    },
                    "description": "Clears the choice, so EuPid again uses the tenant's default authorization server and pre-authorized offers work. It can take a few seconds to apply."
                  },
                  "response": []
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "walletProxySlug",
      "value": "wallet-proxy",
      "type": "string",
      "description": "Slug of the hosted wallet-proxy authorization server."
    },
    {
      "key": "walletClientId",
      "value": "postman-wallet",
      "type": "string",
      "description": "Public client of the wallet proxy used by the Postman wallet."
    },
    {
      "key": "azureProviderId",
      "value": "azure-key-vault",
      "type": "string",
      "description": "Provider id for the tenant's own Azure Key Vault."
    },
    {
      "key": "sharedAzureProviderId",
      "value": "shared-azure-key-vault",
      "type": "string",
      "description": "Provider id for the platform-shared Azure Key Vault."
    },
    {
      "key": "did",
      "value": "",
      "type": "string",
      "description": "The tenant DID, did:web:<tenantSlug>.<baseDomain>."
    },
    {
      "key": "didEncoded",
      "value": "",
      "type": "string",
      "description": "URL-encoded tenant DID."
    },
    {
      "key": "capturedForTenant",
      "value": "",
      "type": "string",
      "description": "The tenantSlug the captured ids belong to."
    },
    {
      "key": "tenantScopedVariables",
      "value": "tenantId,onboardingCorrelationId,ownerActivationLink,authorizationServerId,serviceClientExists,kmsResourceHandle,kmsProviderId,keyAlias,signature,azureResourceHandle,azureResourceVersion,azureCredentialSecretRef,sharedProviderId,sharedAzureResourceHandle,sharedAzureResourceVersion,sharedAzureCredentialSecretRef,issuerId,credentialIssuer,designId,statusListId,signingKmsResourceHandle,signingKeyAlias,signingVerificationMethodId,employeeBadgeStatusListId,employeeBadgeDesignId,employeeBadgeResourceId,issuanceTemplateId,offerCorrelationId,credentialOfferUri,txCode,txCodeRequired,preAuthorizedCode,issuerMetadataUrl,credentialEndpoint,nonceEndpoint,walletAuthorizationServer,walletTokenEndpoint,walletAccessToken,credentialIdentifier,cNonce,proofJwt,holderPrivateKey,holderPublicJwk,credentialStatusIndex,credentialStatusListUri,credentialStatusListId,verifierId,employeeBadgeQueryExists,employeeBadgeQueryBound,verificationTemplateId,verificationCorrelationId,requestObjectUri,trustDomainId,walletProxyId,walletClientExists,keycloakServerId,keycloakServerRevision,keycloakServerLifecycle,federationBindingId,federationBindingEnabled,issuerBindingId,euPidAuthorizationServerRevision,issuerState",
      "type": "string",
      "description": "Variables cleared when tenantSlug changes."
    },
    {
      "key": "tenantId",
      "value": "",
      "type": "string",
      "description": "Tenant id. Set by \"List tenants\", \"List authorization servers\" or the service client token."
    },
    {
      "key": "onboardingCorrelationId",
      "value": "",
      "type": "string",
      "description": "Onboarding of the tenant registered in this session."
    },
    {
      "key": "ownerActivationLink",
      "value": "",
      "type": "string",
      "description": "Owner activation link returned when no email transport is configured."
    },
    {
      "key": "authorizationServerId",
      "value": "",
      "type": "string",
      "description": "Default authorization server of the tenant."
    },
    {
      "key": "serviceClientExists",
      "value": "",
      "type": "string"
    },
    {
      "key": "kmsResourceHandle",
      "value": "",
      "type": "string",
      "description": "KMS resource handle (krh_...)."
    },
    {
      "key": "kmsProviderId",
      "value": "",
      "type": "string",
      "description": "Runtime provider id of that KMS resource."
    },
    {
      "key": "keyAlias",
      "value": "",
      "type": "string",
      "description": "Alias of the key generated by \"Generate a signing key\"."
    },
    {
      "key": "signature",
      "value": "",
      "type": "string"
    },
    {
      "key": "azureResourceHandle",
      "value": "",
      "type": "string"
    },
    {
      "key": "azureResourceVersion",
      "value": "",
      "type": "string"
    },
    {
      "key": "azureCredentialSecretRef",
      "value": "",
      "type": "string"
    },
    {
      "key": "sharedProviderId",
      "value": "",
      "type": "string"
    },
    {
      "key": "sharedAzureResourceHandle",
      "value": "",
      "type": "string"
    },
    {
      "key": "sharedAzureResourceVersion",
      "value": "",
      "type": "string"
    },
    {
      "key": "sharedAzureCredentialSecretRef",
      "value": "",
      "type": "string"
    },
    {
      "key": "issuerId",
      "value": "",
      "type": "string",
      "description": "Credential issuer id."
    },
    {
      "key": "credentialIssuer",
      "value": "",
      "type": "string",
      "description": "Credential issuer identifier (URL)."
    },
    {
      "key": "designId",
      "value": "",
      "type": "string"
    },
    {
      "key": "statusListId",
      "value": "",
      "type": "string"
    },
    {
      "key": "signingKmsResourceHandle",
      "value": "",
      "type": "string"
    },
    {
      "key": "signingKeyAlias",
      "value": "",
      "type": "string"
    },
    {
      "key": "signingVerificationMethodId",
      "value": "",
      "type": "string"
    },
    {
      "key": "employeeBadgeStatusListId",
      "value": "",
      "type": "string"
    },
    {
      "key": "employeeBadgeDesignId",
      "value": "",
      "type": "string"
    },
    {
      "key": "employeeBadgeResourceId",
      "value": "",
      "type": "string"
    },
    {
      "key": "issuanceTemplateId",
      "value": "",
      "type": "string"
    },
    {
      "key": "offerCorrelationId",
      "value": "",
      "type": "string"
    },
    {
      "key": "credentialOfferUri",
      "value": "",
      "type": "string"
    },
    {
      "key": "txCode",
      "value": "",
      "type": "string",
      "description": "Transaction code of the last offer that asked for one."
    },
    {
      "key": "txCodeRequired",
      "value": "",
      "type": "string"
    },
    {
      "key": "preAuthorizedCode",
      "value": "",
      "type": "string"
    },
    {
      "key": "issuerMetadataUrl",
      "value": "",
      "type": "string"
    },
    {
      "key": "credentialEndpoint",
      "value": "",
      "type": "string"
    },
    {
      "key": "nonceEndpoint",
      "value": "",
      "type": "string"
    },
    {
      "key": "walletAuthorizationServer",
      "value": "",
      "type": "string"
    },
    {
      "key": "walletTokenEndpoint",
      "value": "",
      "type": "string"
    },
    {
      "key": "walletAccessToken",
      "value": "",
      "type": "string",
      "description": "Access token of the test wallet (not an administration token)."
    },
    {
      "key": "credentialIdentifier",
      "value": "",
      "type": "string"
    },
    {
      "key": "cNonce",
      "value": "",
      "type": "string"
    },
    {
      "key": "proofJwt",
      "value": "",
      "type": "string"
    },
    {
      "key": "holderPrivateKey",
      "value": "",
      "type": "string",
      "description": "Test wallet key, generated in the Postman sandbox."
    },
    {
      "key": "holderPublicJwk",
      "value": "",
      "type": "string"
    },
    {
      "key": "credentialStatusIndex",
      "value": "",
      "type": "string"
    },
    {
      "key": "credentialStatusListUri",
      "value": "",
      "type": "string"
    },
    {
      "key": "credentialStatusListId",
      "value": "",
      "type": "string"
    },
    {
      "key": "verifierId",
      "value": "",
      "type": "string"
    },
    {
      "key": "employeeBadgeQueryExists",
      "value": "",
      "type": "string"
    },
    {
      "key": "employeeBadgeQueryBound",
      "value": "",
      "type": "string"
    },
    {
      "key": "verificationTemplateId",
      "value": "",
      "type": "string"
    },
    {
      "key": "verificationCorrelationId",
      "value": "",
      "type": "string"
    },
    {
      "key": "requestObjectUri",
      "value": "",
      "type": "string"
    },
    {
      "key": "trustDomainId",
      "value": "",
      "type": "string"
    },
    {
      "key": "walletProxyId",
      "value": "",
      "type": "string"
    },
    {
      "key": "walletClientExists",
      "value": "",
      "type": "string"
    },
    {
      "key": "keycloakServerId",
      "value": "",
      "type": "string"
    },
    {
      "key": "keycloakServerRevision",
      "value": "",
      "type": "string"
    },
    {
      "key": "keycloakServerLifecycle",
      "value": "",
      "type": "string"
    },
    {
      "key": "federationBindingId",
      "value": "",
      "type": "string"
    },
    {
      "key": "federationBindingEnabled",
      "value": "",
      "type": "string"
    },
    {
      "key": "issuerBindingId",
      "value": "",
      "type": "string"
    },
    {
      "key": "euPidAuthorizationServerRevision",
      "value": "",
      "type": "string"
    },
    {
      "key": "issuerState",
      "value": "",
      "type": "string"
    }
  ]
}
