{
  "info": {
    "name": "Location Verification v2 - CIBA - Client Secret",
    "description": "Location Verification v2 (SEP/CAMARA), CIBA grant variant.\n\nClient authentication: CLIENT_SECRET using HTTP Basic authentication (client_id/client_secret). Private Key JWT is also available separately and is recommended because it will become mandatory in the future.\n\nbase_url defaults to Germany staging (https://stg.api.telekom.com). Change only that one variable to target production or another country (see the base_url variable description).\n\nCIBA requires the subscriber to approve the authentication request out-of-band (e.g. on their device) between request 1 and request 2 completing successfully.\n\nCurrently listed for: 🇩🇪 Germany. (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"
  },
  "item": [
    {
      "name": "1) BC-Authorize (start CIBA flow)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const json = pm.response.json();",
              "if (json.auth_req_id) {",
              "    pm.collectionVariables.set('auth_req_id', json.auth_req_id);",
              "    pm.collectionVariables.set('interval', String(json.interval || 5));",
              "    pm.collectionVariables.set('expires_in', String(json.expires_in || 120));",
              "    pm.collectionVariables.set('poll_attempts', '0');",
              "    console.log('auth_req_id captured, interval=' + (json.interval || 5) + 's. Approve the request on the subscriber device, then run the next request (it will auto-poll).');",
              "} else {",
              "    console.error('No auth_req_id in response:', JSON.stringify(json));",
              "}",
              "pm.test('bc-authorize response has auth_req_id', function () {",
              "    pm.expect(json.auth_req_id, JSON.stringify(json)).to.be.a('string');",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded",
            "type": "text"
          }
        ],
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "scope",
              "value": "{{scope}}",
              "type": "text"
            },
            {
              "key": "login_hint",
              "value": "tel:{{phone_number}}",
              "type": "text"
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/bc-authorize",
          "host": [
            "{{base_url}}/bc-authorize"
          ]
        },
        "description": "Starts a CIBA (Client Initiated Backchannel Authentication) flow for the subscriber tel:{{phone_number}}. Captures auth_req_id, interval and expires_in into collection variables for the next (polling) 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, auto-retries)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const MAX_ATTEMPTS = 24; // ~2 minutes at the default 5s interval",
              "let json = {};",
              "try { json = pm.response.json(); } catch (e) { json = {}; }",
              "const attempts = parseInt(pm.collectionVariables.get('poll_attempts') || '0', 10);",
              "const intervalSeconds = parseInt(pm.collectionVariables.get('interval') || '5', 10);",
              "",
              "if (json.access_token) {",
              "    pm.collectionVariables.set('access_token', json.access_token);",
              "    console.log('access_token captured after ' + attempts + ' poll(s). Advancing to the service request.');",
              "    postman.setNextRequest('Location Verification v2 - Verify');",
              "} else if (json.error === 'authorization_pending' || json.error === 'slow_down') {",
              "    const nextAttempts = attempts + 1;",
              "    pm.collectionVariables.set('poll_attempts', String(nextAttempts));",
              "    if (json.error === 'slow_down') {",
              "        pm.collectionVariables.set('interval', String(intervalSeconds + 5));",
              "    }",
              "    if (nextAttempts >= MAX_ATTEMPTS) {",
              "        console.error('CIBA polling gave up after ' + nextAttempts + ' attempts.');",
              "        postman.setNextRequest(null);",
              "    } else {",
              "        console.log('CIBA pending (' + json.error + '), attempt ' + nextAttempts + '/' + MAX_ATTEMPTS + '. Waiting ' + intervalSeconds + 's before retrying.');",
              "        setTimeout(function () {",
              "            postman.setNextRequest('2) Poll Token (CIBA, auto-retries)');",
              "        }, intervalSeconds * 1000);",
              "    }",
              "} else {",
              "    console.error('CIBA polling stopped due to error:', JSON.stringify(json));",
              "    postman.setNextRequest(null);",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded",
            "type": "text"
          }
        ],
        "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 request. A fresh client_assertion is signed on every poll. Auto-retries on authorization_pending/slow_down (respecting the server's interval), capped at ~24 attempts (~2 minutes), then auto-advances to 'Location Verification v2 - Verify' once access_token is captured. Run request 1 first.\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": "Location Verification v2 - Verify",
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const body = {",
              "    area: {",
              "        areaType: 'CIRCLE',",
              "        center: {",
              "            latitude: Number(pm.variables.get('latitude')),",
              "            longitude: Number(pm.variables.get('longitude'))",
              "        },",
              "        radius: Number(pm.variables.get('radius'))",
              "    }",
              "};",
              "const maxAgeRaw = String(pm.variables.get('max_age') ?? '').trim();",
              "if (maxAgeRaw !== '') {",
              "    const maxAge = Number(maxAgeRaw);",
              "    if (!Number.isFinite(maxAge)) throw new Error('max_age must be blank or a numeric value in seconds.');",
              "    body.maxAge = maxAge;",
              "}",
              "pm.request.body.update(JSON.stringify(body, null, 4));"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}",
            "type": "text"
          },
          {
            "key": "Content-Type",
            "value": "application/json",
            "type": "text"
          },
          {
            "key": "x-correlator",
            "value": "{{$guid}}",
            "type": "text"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n    \"area\": {\n        \"areaType\": \"CIRCLE\",\n        \"center\": {\n            \"latitude\": {{latitude}},\n            \"longitude\": {{longitude}}\n        },\n        \"radius\": {{radius}}\n    },\n    \"maxAge\": 120\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "url": {
          "raw": "{{base_url}}/location-verification/v2/verify",
          "host": [
            "{{base_url}}/location-verification/v2/verify"
          ]
        },
        "description": "CAMARA Location Verification v2 - verifies the subscriber's device is within the given circular area. Field shape copied verbatim from VerifyLocationV2 in SEPAPIs.cs."
      },
      "response": []
    },
    {
      "name": "Health Check",
      "event": [],
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Authorization",
            "value": "Bearer {{access_token}}",
            "type": "text"
          },
          {
            "key": "x-correlator",
            "value": "{{$guid}}",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{base_url}}/location-verification/v2/health",
          "host": [
            "{{base_url}}/location-verification/v2/health"
          ]
        },
        "description": "Health check endpoint for this API. Returns HTTP success if the service is reachable."
      },
      "response": []
    }
  ],
  "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. 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 when read inside pre-request/test scripts via pm.collectionVariables.get() - only base_url itself needs to be correct."
    },
    {
      "key": "scope",
      "value": "openid location-verification:verify dpv:FraudPreventionAndDetection",
      "type": "string",
      "description": "openid location-verification:verify dpv:FraudPreventionAndDetection - openid is prefixed because CIBA (urn:openid:params:grant-type:ciba) always requires the openid scope for the backchannel authentication request to succeed."
    },
    {
      "key": "client_id",
      "value": "",
      "type": "string",
      "description": "REQUIRED. Your registered OAuth client ID (PRIVATE_KEY_JWT client authentication)."
    },
    {
      "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": "phone_number",
      "value": "+4915174466709",
      "type": "string",
      "description": "Standard test MSISDN for this API from this app's live DE staging standard configuration (SepStandardApiConfigurations table) - not a generic sample number."
    },
    {
      "key": "latitude",
      "value": "52.5200",
      "type": "string",
      "description": "Sample latitude (Berlin Alexanderplatz - central, well-covered urban location). If you still get an INVALID_ARGUMENT/'area.center.latitude' error, verify with your account manager which coordinates are valid for your test subscriber's registered network location."
    },
    {
      "key": "longitude",
      "value": "13.4050",
      "type": "string",
      "description": "Sample longitude (Berlin Alexanderplatz - central, well-covered urban location). If you still get an INVALID_ARGUMENT/'area.center.longitude' error, verify with your account manager which coordinates are valid for your test subscriber's registered network location."
    },
    {
      "key": "radius",
      "value": "10000",
      "type": "string",
      "description": "Verification radius in meters (1-200000 per SEPAPIs.cs validation). Increased from the app's UI default of 2000 to 10000 - some deployments reject radius values too close to the CAMARA spec's minimum boundary with an INVALID_ARGUMENT/'area.radius' validation error; increase further if you still see that error."
    },
    {
      "key": "max_age",
      "value": "120",
      "type": "string",
      "description": "seconds, 0 requests fresh calculation, blank omits maxAge."
    },
    {
      "key": "access_token",
      "value": "",
      "type": "string",
      "description": "Auto-populated by the token request. Do not edit manually."
    },
    {
      "key": "auth_req_id",
      "value": "",
      "type": "string",
      "description": "Auto-populated by the bc-authorize request. Do not edit manually."
    },
    {
      "key": "interval",
      "value": "5",
      "type": "string",
      "description": "Polling interval in seconds - auto-populated/updated by bc-authorize and polling responses."
    },
    {
      "key": "expires_in",
      "value": "",
      "type": "string",
      "description": "Auto-populated by the bc-authorize request."
    },
    {
      "key": "poll_attempts",
      "value": "0",
      "type": "string",
      "description": "Auto-populated poll counter - resets to 0 on each new bc-authorize."
    }
  ]
}
