{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.13.0"
    },
    "blog_metadata": {
      "topic": "How Azure API Management’s AI Gateway Could Become the Control Point for Enterprise AI",
      "slug": "how-azure-api-management-s-ai-gateway-could-become-the-contr",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-07-28T22:45:30.709Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# How Azure API Management’s AI Gateway Could Become the Control Point for Enterprise AI\n",
        "\n",
        "This notebook turns the blog post into a hands-on validation flow. The core idea is that Azure API Management (APIM) AI Gateway is valuable less as a model feature and more as a governance control point for routing, identity, observability, and token consumption.\n",
        "\n",
        "The examples below show how to simulate that control point with Python, inspect policy artifacts, and emit lightweight telemetry that could support platform, security, and FinOps workflows."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install requests python-dotenv lxml"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import os\n",
        "import json\n",
        "import time\n",
        "from textwrap import dedent\n",
        "from xml.etree import ElementTree as ET\n",
        "\n",
        "import requests"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Control point architecture\n",
        "\n",
        "This example converts the blog's gateway pattern into a Python representation you can inspect in a notebook. It mirrors the intended flow: applications call APIM, APIM applies governance controls, and APIM routes to approved model providers while sending telemetry to downstream monitoring and cost systems."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "architecture = {\n",
        "    \"apps\": [\"Apps\", \"Copilots\", \"Agents\"],\n",
        "    \"gateway\": \"Azure API Management AI Gateway\",\n",
        "    \"controls\": [\n",
        "        \"Auth + Token Validation\",\n",
        "        \"Rate Limits + Quotas\",\n",
        "        \"Prompt / Response Policies\",\n",
        "        \"Observability + Cost Tracking\",\n",
        "        \"Model Routing\"\n",
        "    ],\n",
        "    \"backends\": [\"Azure OpenAI\", \"OpenAI / Anthropic / Other LLMs\"],\n",
        "    \"downstream\": [\"Log Analytics\", \"SIEM\", \"FinOps\"]\n",
        "}\n",
        "\n",
        "print(json.dumps(architecture, indent=2))\n",
        "\n",
        "print(\"\\nValidation checks:\")\n",
        "print(\"- Centralized entry point:\", architecture[\"gateway\"])\n",
        "print(\"- Number of governance controls:\", len(architecture[\"controls\"]))\n",
        "print(\"- Backend options:\", \", \".join(architecture[\"backends\"]))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Required environment variables for gateway calls\n",
        "\n",
        "The next example uses a bearer token stored in an environment variable.\n",
        "\n",
        "Required variables:\n",
        "- `APIM_TOKEN`: bearer token used to authenticate to the APIM gateway\n",
        "\n",
        "Optional variables:\n",
        "- `APIM_GATEWAY_URL`: overrides the default sample URL"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Call APIM instead of the model endpoint directly\n",
        "\n",
        "This example demonstrates the blog's main operational pattern: the application calls the APIM AI Gateway URL rather than a provider endpoint directly. To keep the notebook safe and runnable without live credentials, the code supports a dry-run mode by default and only performs the HTTP request if a token is present."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import os\n",
        "import json\n",
        "import requests\n",
        "\n",
        "\n",
        "gateway_url = os.getenv(\"APIM_GATEWAY_URL\", \"https://contoso-apim.azure-api.net/ai/chat/completions\")\n",
        "apim_token = os.getenv(\"APIM_TOKEN\")\n",
        "\n",
        "headers = {\n",
        "    \"Authorization\": f\"Bearer {apim_token}\" if apim_token else \"Bearer <missing-token>\",\n",
        "    \"Content-Type\": \"application/json\",\n",
        "}\n",
        "\n",
        "payload = {\n",
        "    \"model\": \"gpt-4o-mini\",\n",
        "    \"messages\": [\n",
        "        {\"role\": \"user\", \"content\": \"Summarize this incident in 3 bullets.\"}\n",
        "    ],\n",
        "}\n",
        "\n",
        "print(\"Gateway URL:\", gateway_url)\n",
        "print(\"Request payload:\")\n",
        "print(json.dumps(payload, indent=2))\n",
        "\n",
        "if apim_token:\n",
        "    try:\n",
        "        response = requests.post(gateway_url, headers=headers, json=payload, timeout=30)\n",
        "        print(\"Status:\", response.status_code)\n",
        "        try:\n",
        "            print(json.dumps(response.json(), indent=2))\n",
        "        except ValueError:\n",
        "            print(response.text)\n",
        "    except requests.RequestException as e:\n",
        "        print(\"Request failed:\", e)\n",
        "else:\n",
        "    print(\"Dry run only: set APIM_TOKEN to execute the live request.\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## APIM policy as a governance artifact\n",
        "\n",
        "The original post included an APIM policy snippet in XML. Since notebook code cells must be valid Python, this example stores the policy as a string, parses it, and validates that the expected governance controls are present: JWT validation, rate limiting, and route stamping."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "policy_xml = dedent('''\n",
        "<policies>\n",
        "  <inbound>\n",
        "    <base />\n",
        "    <validate-jwt header-name=\"Authorization\" require-scheme=\"Bearer\" />\n",
        "    <rate-limit-by-key calls=\"20\" renewal-period=\"60\"\n",
        "      counter-key=\"@(context.Subscription?.Key ?? context.Request.IpAddress)\" />\n",
        "    <set-header name=\"x-ai-route\" exists-action=\"override\">\n",
        "      <value>approved-enterprise-model</value>\n",
        "    </set-header>\n",
        "  </inbound>\n",
        "  <backend><base /></backend>\n",
        "  <outbound><base /></outbound>\n",
        "</policies>\n",
        "''').strip()\n",
        "\n",
        "root = ET.fromstring(policy_xml)\n",
        "inbound = root.find(\"inbound\")\n",
        "\n",
        "checks = {\n",
        "    \"validate_jwt_present\": inbound.find(\"validate-jwt\") is not None,\n",
        "    \"rate_limit_present\": inbound.find(\"rate-limit-by-key\") is not None,\n",
        "    \"route_header_present\": inbound.find(\"set-header\") is not None,\n",
        "}\n",
        "\n",
        "route_header = inbound.find(\"set-header\")\n",
        "route_name = route_header.attrib.get(\"name\") if route_header is not None else None\n",
        "route_value = route_header.findtext(\"value\") if route_header is not None else None\n",
        "\n",
        "print(\"Policy XML:\\n\")\n",
        "print(policy_xml)\n",
        "print(\"\\nValidation results:\")\n",
        "print(json.dumps(checks, indent=2))\n",
        "print(\"Route header:\", route_name)\n",
        "print(\"Route value:\", route_value)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Simulate APIM named value creation for backend secrets\n",
        "\n",
        "The blog included a PowerShell example for creating a named value in APIM. Here, we model the same concept in Python by building a deployment payload that could be sent to infrastructure automation. This helps validate the separation of concerns: applications call APIM, while backend secrets remain managed by the platform."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "named_value_payload = {\n",
        "    \"resourceGroupName\": \"rg-ai-platform\",\n",
        "    \"serviceName\": \"contoso-apim\",\n",
        "    \"namedValueId\": \"aoai-key\",\n",
        "    \"displayName\": \"aoai-key\",\n",
        "    \"secret\": True,\n",
        "    \"value\": \"replace-with-real-key\"\n",
        "}\n",
        "\n",
        "safe_preview = dict(named_value_payload)\n",
        "safe_preview[\"value\"] = \"***REDACTED***\"\n",
        "\n",
        "print(\"Simulated APIM named value payload:\")\n",
        "print(json.dumps(safe_preview, indent=2))\n",
        "\n",
        "print(\"\\nValidation checks:\")\n",
        "print(\"- Secret flag set:\", named_value_payload[\"secret\"])\n",
        "print(\"- Named value id:\", named_value_payload[\"namedValueId\"])\n",
        "print(\"- Service name:\", named_value_payload[\"serviceName\"])"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Emit lightweight gateway telemetry\n",
        "\n",
        "This example shows how a platform team could capture governance-friendly telemetry from the gateway path. It creates an event with app identity, route, estimated tokens, and latency, then supports either a dry run or a live POST to a telemetry endpoint."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Required environment variables for telemetry\n",
        "\n",
        "Optional variables:\n",
        "- `TELEMETRY_URL`: endpoint that accepts gateway telemetry events\n",
        "\n",
        "If `TELEMETRY_URL` is not set, the notebook will print the event instead of sending it."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import time\n",
        "import json\n",
        "import os\n",
        "import requests\n",
        "\n",
        "telemetry_url = os.getenv(\"TELEMETRY_URL\")\n",
        "\n",
        "event = {\n",
        "    \"app\": \"claims-copilot\",\n",
        "    \"user\": \"alice@contoso.com\",\n",
        "    \"model_route\": \"approved-enterprise-model\",\n",
        "    \"tokens_estimated\": 1450,\n",
        "    \"latency_ms\": 820,\n",
        "    \"timestamp\": int(time.time()),\n",
        "}\n",
        "\n",
        "print(\"Telemetry event:\")\n",
        "print(json.dumps(event, indent=2))\n",
        "\n",
        "if telemetry_url:\n",
        "    try:\n",
        "        response = requests.post(telemetry_url, json=event, timeout=10)\n",
        "        print(\"Telemetry POST status:\", response.status_code)\n",
        "        print(\"telemetry sent\")\n",
        "    except requests.RequestException as e:\n",
        "        print(\"Telemetry send failed:\", e)\n",
        "else:\n",
        "    print(\"Dry run only: set TELEMETRY_URL to send the event.\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Evaluate the four strategic outcomes\n",
        "\n",
        "The blog argues that APIM AI Gateway should be judged on four outcomes only: centralized routing, clean access policy enforcement, usable interaction telemetry, and governable token consumption. This cell turns that framing into a simple scorecard you can adapt for your environment."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "scorecard = {\n",
        "    \"centralize_routing\": True,\n",
        "    \"enforce_access_policy\": True,\n",
        "    \"usable_interaction_telemetry\": True,\n",
        "    \"governable_token_consumption\": True,\n",
        "}\n",
        "\n",
        "print(\"APIM AI Gateway scorecard:\")\n",
        "print(json.dumps(scorecard, indent=2))\n",
        "\n",
        "all_green = all(scorecard.values())\n",
        "print(\"\\nRecommended posture:\", \"Establish the control point early\" if all_green else \"Investigate gaps before scaling\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Summary\n",
        "\n",
        "This notebook validated the blog's central claim in a practical way: APIM AI Gateway can act as a control point for enterprise AI by standardizing the path for routing, authentication, policy enforcement, telemetry, and cost visibility.\n",
        "\n",
        "## Next Steps\n",
        "\n",
        "1. Replace the sample gateway URL with a real APIM endpoint.\n",
        "2. Test JWT validation, throttling, and route headers in a non-production APIM instance.\n",
        "3. Connect telemetry to Log Analytics, SIEM, or FinOps dashboards.\n",
        "4. Define platform ownership for approved endpoints, quotas, logging, and exception handling.\n",
        "5. Review broader governance requirements using Azure's Cloud Adoption Framework."
      ]
    }
  ]
}