{
  "info": {
    "name": "KYC Match v0.2 - CIBA - Client Secret",
    "_postman_id": "kycmatch-v02-de-staging-ciba",
    "description": "KYC Match v0.2 (kyc-match/v0.2/match) using the CIBA grant (urn:openid:params:grant-type:ciba). Client authentication is PRIVATE_KEY_JWT only (RS256-signed client_assertion) - never client_secret. See the base_url variable description for the default environment and how to switch country/environment.\n\nScope is the exact string used by CheckKycMatchEndpoint in SEPAPIs.cs: \"dpv:FraudPreventionAndDetection kyc-match:match\". This app only prefixes scopes with \"openid\" when its UsesOpenIdScope setting is active for a given credential profile/environment (see SEPAPIs.cs ResolveScope calls) - that is environment-specific, not fixed for KYC Match, so this collection ships the base scope as-is. If your real OAuth server requires an \"openid\" scope for CIBA or Authorization Code token issuance, prepend it yourself in the `scope` variable (e.g. \"openid dpv:FraudPreventionAndDetection kyc-match:match\").\n\nCurrently listed for: 🇩🇪 Germany, 🇦🇹 Austria. (Source: this app's live product-catalog grant-type configuration, staging environment - see the /documentation/postman-collections page for the current matrix.)\n\nIMPORTANT: only one version of this API family can be ordered onto a single TMF Application - if your client_id/private_key were provisioned for KYC Match v0.3 instead of this one (e.g. you ordered Sim Swap v1 but not v2, or vice versa), authentication will fail here with a 401 UNAUTHENTICATED/InvalidClaim error even though your credentials are otherwise valid and correctly entered. You need a client_id/private_key from an Application that specifically ordered THIS version to use this collection.\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",
      "type": "string",
      "description": "Single variable driving every request in this collection. Default is Germany STAGING (https://stg.api.telekom.com). Change ONLY this variable to switch environment/country:\n- Germany production: https://api.telekom.com\n- Austria staging: https://at.stg.api.telekom.com | production: https://at.api.telekom.com\n- Poland staging: https://pl.stg.api.telekom.com | production: https://pl.api.telekom.com\n- Greece staging: https://gr.stg.api.telekom.com | production: https://gr.api.telekom.com\nSee the /documentation/endpoints page in the MBAPI app for the full country/environment reference table. 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": "scope",
      "value": "openid dpv:FraudPreventionAndDetection kyc-match:match",
      "type": "string",
      "description": "openid dpv:FraudPreventionAndDetection kyc-match:match - CIBA (urn:openid:params:grant-type:ciba) always requires the openid scope for the backchannel authentication request to succeed, so it is prefixed here by default (unlike the JWT Bearer variant, where openid is not required). See SEPAPIs.cs UsesOpenIdScope / ResolveScope for how this app itself decides when to prefix openid per grant type."
    },
    {
      "key": "phone_number",
      "value": "+491702049821",
      "type": "string",
      "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": "client_id",
      "value": "",
      "type": "string",
      "description": "Your registered OAuth client ID (PRIVATE_KEY_JWT client authentication). Required - never ships with a real value."
    },
    {
      "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": "access_token",
      "value": "",
      "type": "string",
      "description": "Auto-populated by the token request's test script once a token is obtained."
    },
    {
      "key": "auth_req_id",
      "value": "",
      "type": "string",
      "description": "Auto-populated by the BC-Authorize request's test script."
    },
    {
      "key": "ciba_interval",
      "value": "5",
      "type": "string",
      "description": "Auto-populated polling interval (seconds) returned by the BC-Authorize call."
    },
    {
      "key": "ciba_expires_in",
      "value": "300",
      "type": "string",
      "description": "Auto-populated auth_req_id expiry (seconds) returned by the BC-Authorize call."
    },
    {
      "key": "ciba_poll_count",
      "value": "0",
      "type": "string",
      "description": "Auto-incremented poll attempt counter; polling stops after ~24 attempts."
    }
  ],
  "item": [
    {
      "name": "1. BC-Authorize (start CIBA flow)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const jsonData = pm.response.json();",
              "if (jsonData.auth_req_id) {",
              "    pm.collectionVariables.set('auth_req_id', jsonData.auth_req_id);",
              "    pm.collectionVariables.set('ciba_interval', String(jsonData.interval || 5));",
              "    pm.collectionVariables.set('ciba_expires_in', String(jsonData.expires_in || 300));",
              "    pm.collectionVariables.set('ciba_poll_count', '0');",
              "    console.log('auth_req_id captured:', jsonData.auth_req_id);",
              "} else {",
              "    console.error('No auth_req_id in bc-authorize response:', pm.response.text());",
              "}",
              "pm.test('bc-authorize response has auth_req_id', function () {",
              "    pm.expect(jsonData.auth_req_id).to.be.a('string').and.not.empty;",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded"
          }
        ],
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "scope",
              "value": "{{scope}}",
              "type": "text"
            },
            {
              "key": "login_hint",
              "value": "tel:{{phone_number}}",
              "type": "text",
              "description": "CIBA login_hint format tel:<E.164 number>, built from the phone_number variable."
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/bc-authorize",
          "host": [
            "{{base_url}}/bc-authorize"
          ]
        },
        "description": "Starts a CIBA (Client Initiated Backchannel Authentication) flow. Captures auth_req_id, interval, and expires_in for polling in the next request.\n\nClient authentication is HTTP Basic using {{client_id}} and {{client_secret}}, matching SEPAPIs.cs CLIENT_SECRET mode; no client_assertion is sent.",
        "auth": {
          "type": "basic",
          "basic": [
            {
              "key": "username",
              "value": "{{client_id}}",
              "type": "string"
            },
            {
              "key": "password",
              "value": "{{client_secret}}",
              "type": "string"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "2. Poll Token (CIBA)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const authReqId = pm.collectionVariables.get('auth_req_id');",
              "const interval = parseInt(pm.collectionVariables.get('ciba_interval') || '5', 10);",
              "let pollCount = parseInt(pm.collectionVariables.get('ciba_poll_count') || '0', 10);",
              "const MAX_POLLS = 24;",
              "",
              "let jsonData = {};",
              "try {",
              "    jsonData = pm.response.json();",
              "} catch (e) {",
              "    console.error('Non-JSON token response:', pm.response.text());",
              "}",
              "",
              "if (jsonData.access_token) {",
              "    pm.collectionVariables.set('access_token', jsonData.access_token);",
              "    console.log('access_token captured after', pollCount, 'poll(s).');",
              "    pm.test('CIBA token obtained', function () {",
              "        pm.expect(jsonData.access_token).to.be.a('string').and.not.empty;",
              "    });",
              "    postman.setNextRequest('3. GET KYC Match Health v0.2 -> then run POST KYC Match v0.2 manually');",
              "} else if (jsonData.error === 'authorization_pending' || jsonData.error === 'slow_down') {",
              "    pollCount += 1;",
              "    pm.collectionVariables.set('ciba_poll_count', String(pollCount));",
              "    if (pollCount >= MAX_POLLS) {",
              "        console.error('CIBA polling gave up after', MAX_POLLS, 'attempts. Last response:', pm.response.text());",
              "        postman.setNextRequest(null);",
              "    } else {",
              "        const waitMs = (jsonData.error === 'slow_down' ? interval + 5 : interval) * 1000;",
              "        console.log('CIBA pending (' + jsonData.error + '), retrying in', waitMs, 'ms. Attempt', pollCount, 'of', MAX_POLLS);",
              "        setTimeout(function () {",
              "            postman.setNextRequest('2. Poll Token (CIBA)');",
              "        }, waitMs);",
              "        return;",
              "    }",
              "} else {",
              "    console.error('CIBA token request failed:', pm.response.text());",
              "    postman.setNextRequest(null);",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded"
          }
        ],
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "grant_type",
              "value": "urn:openid:params:grant-type:ciba",
              "type": "text"
            },
            {
              "key": "auth_req_id",
              "value": "{{auth_req_id}}",
              "type": "text"
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/token",
          "host": [
            "{{base_url}}/token"
          ]
        },
        "description": "Polls the token endpoint for the outcome of the CIBA flow. Auto-retries on authorization_pending/slow_down (honoring the server's interval, capped at 24 attempts) via postman.setNextRequest + setTimeout, and auto-advances to the KYC Match request once access_token is captured.\n\nClient authentication is HTTP Basic using {{client_id}} and {{client_secret}}, matching SEPAPIs.cs CLIENT_SECRET mode; no client_assertion is sent.",
        "auth": {
          "type": "basic",
          "basic": [
            {
              "key": "username",
              "value": "{{client_id}}",
              "type": "string"
            },
            {
              "key": "password",
              "value": "{{client_secret}}",
              "type": "string"
            }
          ]
        }
      },
      "response": []
    },
    {
      "name": "POST KYC Match v0.2",
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// No signing needed here - just reuses {{access_token}} captured by the token request(s) above."
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "x-correlator",
            "value": "{{$guid}}",
            "description": "Unique correlation ID for tracing this request through the API gateway/backend."
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phoneNumber\": \"{{phone_number}}\",\n  \"givenName\": \"Max\",\n  \"familyName\": \"Mustermann\",\n  \"address\": \"Musterstraße 1\",\n  \"streetName\": \"Musterstraße\",\n  \"streetNumber\": \"1\",\n  \"postalCode\": \"10115\",\n  \"locality\": \"Berlin\",\n  \"country\": \"DE\",\n  \"birthdate\": \"1980-01-01\",\n  \"email\": \"max.mustermann@example.de\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/kyc-match/v0.2/match",
          "host": [
            "{{base_url}}/kyc-match/v0.2/match"
          ]
        },
        "description": "Calls the KYC Match v0.2 resource (kyc-match/v0.2/match) with sample subscriber data (see the phone_number variable for the live DE staging standard-config MSISDN). Field names copied verbatim from CheckKycMatchEndpoint in SEPAPIs.cs."
      },
      "response": []
    },
    {
      "name": "GET KYC Match Health v0.2",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}"
          },
          {
            "key": "x-correlator",
            "value": "{{$guid}}",
            "description": "Unique correlation ID for tracing this request through the API gateway/backend."
          }
        ],
        "url": {
          "raw": "{{base_url}}/kyc-match/v0.2/health",
          "host": [
            "{{base_url}}/kyc-match/v0.2/health"
          ]
        },
        "description": "Health check for the KYC Match v0.2 service. Run after obtaining access_token from any of the token requests in this collection."
      },
      "response": []
    }
  ]
}
