{
  "info": {
    "name": "Number Verification v1 - Authorization Code - Client Secret",
    "description": "CAMARA/SEP Number Verification v1 (NV1), resource path number-verification/v1/verify, using the AUTHORIZATION_CODE grant (the only grant type this app supports for NV1) with CLIENT_SECRET HTTP Basic client authentication (RS256 client_assertion signed with your own private key - never a client secret).\n\nNV1 is a NETWORK-BASED / silent authorize flow: the /authorize step must be opened in a real mobile browser on the subscriber's cellular data connection (not Wi-Fi, not inside Postman) so the mobile network operator can silently identify the subscriber and redirect back with an authorization code - there is no login/consent screen and no phone number field in the API calls themselves.\n\nHow to use this collection:\n1. Fill in client_id, private_key (PKCS8 PEM), and optionally kid in the collection variables.\n2. Run request 1 to build the /authorize URL, then open the generated URL (see the Postman console log, or copy request 1's URL) in a real mobile browser on cellular data. Approve/observe the redirect back to your redirect_uri and copy the `code` query parameter value.\n3. Paste that code into the auth_code collection variable.\n4. Run request 2 to exchange the code for an access token (this signs a PRIVATE_KEY_JWT client_assertion locally in the pre-request script - your private key never leaves your machine/Postman instance).\n5. Run request 3 to call Number Verification v1 Verify with the resulting access token.\n\nSwitching country/environment: change ONLY the base_url collection variable. Defaults to Germany staging (https://stg.api.telekom.com). Other options: Germany production https://api.telekom.com; Austria staging https://at.stg.api.telekom.com / production https://at.api.telekom.com; Poland staging https://pl.stg.api.telekom.com / production https://pl.api.telekom.com; Greece staging https://gr.stg.api.telekom.com / production https://gr.api.telekom.com. See this app's /documentation/endpoints page for the full reference.\n\nCurrently listed for: 🇩🇪 Germany, 🇬🇷 Greece, 🇵🇱 Poland, 🇦🇹 Austria. (Source: this app's live product-catalog grant-type configuration, staging environment - see the /documentation/postman-collections page for the current matrix.)\n\nThis is the Client Secret authentication counterpart. It ships with empty client_id and client_secret variables. CIBA uses HTTP Basic at /bc-authorize and /token. JWT Bearer still signs only its required subscriber grant assertion locally and authenticates the client with HTTP Basic, matching SEPAPIs.cs.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://stg.api.telekom.com",
      "description": "Single variable that drives every endpoint in this collection. Default is Germany staging. Change ONLY this variable to switch environment/country: Germany production https://api.telekom.com; Austria staging https://at.stg.api.telekom.com / production https://at.api.telekom.com; Poland staging https://pl.stg.api.telekom.com / production https://pl.api.telekom.com; Greece staging https://gr.stg.api.telekom.com / production https://gr.api.telekom.com. See the app's /documentation/endpoints page for the full reference. Every request and script in this collection builds the full endpoint path directly from base_url (e.g. {{base_url}}/token) rather than through a separate derived variable, because Postman does not resolve nested variable references (a variable whose value itself contains {{...}}) when read inside pre-request/test scripts via pm.collectionVariables.get() - only base_url itself needs to be correct. "
    },
    {
      "key": "client_id",
      "value": "",
      "description": "Your registered client ID for Number Verification v1. Fill this in - ships empty."
    },
    {
      "key": "client_secret",
      "value": "",
      "type": "string",
      "description": "REQUIRED. Your registered OAuth client secret for this API/application. Fill this in locally; this downloadable collection intentionally ships without a secret."
    },
    {
      "key": "redirect_uri",
      "value": "https://yourapp.com/callback",
      "description": "The redirect URI registered for your client, used in the /authorize request and the token exchange."
    },
    {
      "key": "auth_code",
      "value": "",
      "description": "Paste the `code` query parameter value here after completing the browser-based authorize step (request 1)."
    },
    {
      "key": "phone_number",
      "value": "+491704042076",
      "description": "Standard test MSISDN for this API from this app's live DE staging standard configuration (SepStandardApiConfigurations table, per-API/country/environment) - not a generic sample number."
    },
    {
      "key": "state",
      "value": "",
      "description": "Randomly generated by request 1's pre-request script - CSRF protection value echoed back on redirect."
    },
    {
      "key": "nonce",
      "value": "",
      "description": "Randomly generated by request 1's pre-request script - OIDC replay protection value."
    },
    {
      "key": "access_token",
      "value": "",
      "description": "Populated automatically by request 2's test script after a successful token exchange."
    }
  ],
  "item": [
    {
      "name": "1 - Build Authorize URL (open in browser)",
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Generate fresh state/nonce for this authorize attempt.",
              "function randomHex(len) {",
              "    var chars = 'abcdef0123456789';",
              "    var out = '';",
              "    for (var i = 0; i < len; i++) out += chars.charAt(Math.floor(Math.random() * chars.length));",
              "    return out;",
              "}",
              "var state = randomHex(24);",
              "var nonce = randomHex(24);",
              "pm.collectionVariables.set('state', state);",
              "pm.collectionVariables.set('nonce', nonce);",
              "",
              "var base = (pm.collectionVariables.get('base_url') + '/authorize');",
              "var clientId = pm.collectionVariables.get('client_id');",
              "var redirectUri = pm.collectionVariables.get('redirect_uri');",
              "var scope = 'openid number-verification:verify dpv:FraudPreventionAndDetection';",
              "",
              "var url = base",
              "    + '?response_type=code'",
              "    + '&client_id=' + encodeURIComponent(clientId)",
              "    + '&redirect_uri=' + encodeURIComponent(redirectUri)",
              "    + '&scope=' + encodeURIComponent(scope)",
              "    + '&state=' + encodeURIComponent(state)",
              "    + '&nonce=' + encodeURIComponent(nonce);",
              "",
              "pm.collectionVariables.set('generated_authorize_url', url);",
              "console.log('--- Number Verification v1: Authorize URL ---');",
              "console.log('Open this URL in a REAL MOBILE BROWSER on CELLULAR DATA (not Wi-Fi, not inside Postman):');",
              "console.log(url);",
              "console.log('After it redirects to your redirect_uri, copy the \"code\" query parameter value into the auth_code collection variable.');"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/authorize?response_type=code&client_id={{client_id}}&redirect_uri={{redirect_uri}}&scope=openid%20number-verification%3Averify%20dpv%3AFraudPreventionAndDetection&state={{state}}&nonce={{nonce}}",
          "host": [
            "{{base_url}}/authorize"
          ],
          "query": [
            {
              "key": "response_type",
              "value": "code"
            },
            {
              "key": "client_id",
              "value": "{{client_id}}"
            },
            {
              "key": "redirect_uri",
              "value": "{{redirect_uri}}"
            },
            {
              "key": "scope",
              "value": "openid number-verification:verify dpv:FraudPreventionAndDetection"
            },
            {
              "key": "state",
              "value": "{{state}}"
            },
            {
              "key": "nonce",
              "value": "{{nonce}}"
            }
          ]
        },
        "description": "This request is FOR REFERENCE / COPY-PASTE ONLY - it is not meant to succeed inside Postman. Number Verification v1 uses a NETWORK-BASED, silent authorize flow: it only works when opened in a real mobile browser on the subscriber's cellular data connection, so the mobile network operator can silently identify the subscriber's phone number and redirect back with an authorization code (there is no login/consent screen). Running this GET from Postman will likely fail, hang, or be rejected because Postman is not a mobile browser on a cellular connection.\n\nSteps:\n1. Send this request once so the pre-request script logs the full authorize URL to the Postman Console (View > Show Postman Console).\n2. Copy that URL from the console (or use the Send button's generated URL) and open it in a real mobile browser on cellular data.\n3. After the silent redirect completes, your browser will land on redirect_uri with a `code` query parameter (and the same `state` you generated).\n4. Copy the `code` value and paste it into this collection's `auth_code` variable, then run request 2."
      }
    },
    {
      "name": "2 - Exchange Code for Token",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Token exchange succeeded', function () {",
              "    pm.response.to.have.status(200);",
              "});",
              "",
              "var json = {};",
              "try { json = pm.response.json(); } catch (e) {}",
              "",
              "if (json && json.access_token) {",
              "    pm.collectionVariables.set('access_token', json.access_token);",
              "    console.log('Captured access_token into collection variable access_token.');",
              "} else {",
              "    console.log('No access_token found in response - check the response body for errors.');",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded"
          }
        ],
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "grant_type",
              "value": "authorization_code"
            },
            {
              "key": "code",
              "value": "{{auth_code}}"
            },
            {
              "key": "redirect_uri",
              "value": "{{redirect_uri}}"
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/token",
          "host": [
            "{{base_url}}/token"
          ]
        },
        "description": "Exchanges the authorization code you pasted into auth_code for an access token, authenticating with PRIVATE_KEY_JWT (a locally-signed RS256 client_assertion) instead of a client secret. The pre-request script signs the JWT entirely in pure JavaScript (BigInt + CryptoJS.SHA256 + atob/btoa) - no Node crypto module is used, so this works in every Postman sandbox version. The signed access_token is captured into the access_token collection variable by the test script for use in request 3.\n\nClient authentication is HTTP Basic using {{client_id}} and {{client_secret}}; no client_assertion is sent.",
        "auth": {
          "type": "basic",
          "basic": [
            {
              "key": "username",
              "value": "{{client_id}}",
              "type": "string"
            },
            {
              "key": "password",
              "value": "{{client_secret}}",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "name": "3 - Verify Number",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Verify call succeeded', function () {",
              "    pm.response.to.have.status(200);",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "x-correlator",
            "value": "{{$guid}}"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phoneNumber\": \"{{phone_number}}\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/number-verification/v1/verify",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "number-verification",
            "v1",
            "verify"
          ]
        },
        "description": "Calls Number Verification v1's verify endpoint with the access token obtained via the network-based authorization_code flow in request 2. The phoneNumber field is included per the CAMARA NV1 spec/SEPAPIs.cs's ExecuteNumberVerificationV1Verify body shape - the API verifies this number against the subscriber identity implicitly established during the silent /authorize redirect (request 1), it is not a lookup by arbitrary number."
      }
    }
  ]
}
