Azure APIM Agent Onboarding

Azure API Management Agent Onboarding — automated agent self-registration on Microsoft Azure APIM. Uses the Product → Subscription → User nested scope model (the cleanest gateway-native scope abstraction in the field), Microsoft Entra service principal short-circuit for first-party agents, Azure Monitor for audit (Tier 2 side-channel — Application Insights custom dimensions carry the Web Bot Auth signature). Runtime policy enforcement (signature verify, consent check, scope classify, audit emit) lives in the orchestration.steps below — each step that gates issuance carries on_failure: deny. Lint-time validation of this capability shape lives in the companion Polychro ruleset at https://github.com/api-evangelist/posts/blob/main/polychro/agent-onboarding-rules.yaml — Polychro is Naftiko's governance layer, separate from the capability spec, and is the correct home for cross-object consistency rules that apply across every agent-onboarding capability.

Run with Naftiko Microsoft AzureAzure APIMMicrosoft EntraAgent OnboardingProductsSubscriptionsAzure MonitorApplication InsightsNaftiko Capability

What You Can Do

POST
Onboardagent
/v1/agents/onboard
POST
Revokeagent
/v1/agents/{agent_id}/revoke

MCP Tools

agent-register

agent-revoke

idempotent

Capability Spec

azure-apim-agent-onboarding.yaml Raw ↑
naftiko: 1.0.0-alpha2
info:
  label: Azure APIM Agent Onboarding
  description: 'Azure API Management Agent Onboarding — automated agent self-registration
    on Microsoft Azure APIM. Uses the Product → Subscription → User nested scope model
    (the cleanest gateway-native scope abstraction in the field), Microsoft Entra service
    principal short-circuit for first-party agents, Azure Monitor for audit (Tier 2
    side-channel — Application Insights custom dimensions carry the Web Bot Auth signature). Runtime policy enforcement (signature verify, consent check, scope classify, audit emit) lives in the orchestration.steps below — each step that gates issuance carries on_failure: deny. Lint-time validation of this capability shape lives in the companion Polychro ruleset at https://github.com/api-evangelist/posts/blob/main/polychro/agent-onboarding-rules.yaml — Polychro is Naftiko''s governance layer, separate from the capability spec, and is the correct home for cross-object consistency rules that apply across every agent-onboarding capability.'
  tags:
  - Microsoft Azure
  - Azure APIM
  - Microsoft Entra
  - Agent Onboarding
  - Products
  - Subscriptions
  - Azure Monitor
  - Application Insights
  - Naftiko Capability
  created: '2026-05-28'
  modified: '2026-05-28'
  related:
  - https://apievangelist.com/2026/05/27/automated-agent-onboarding-is-a-naftiko-capability-not-a-gateway-feature/
  - https://github.com/api-evangelist/microsoft-azure-api-management
binds:
- namespace: env
  keys:
    AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_ID
    AZURE_RG_NAME: AZURE_RG_NAME
    AZURE_APIM_SERVICE_NAME: AZURE_APIM_SERVICE_NAME
    AZURE_BEARER_TOKEN: AZURE_BEARER_TOKEN
    APPINSIGHTS_WORKSPACE_ID: APPINSIGHTS_WORKSPACE_ID
    AGENT_TRUSTED_ISSUERS: AGENT_TRUSTED_ISSUERS
    AGENT_TRUSTED_ENTRA_PRINCIPALS: AGENT_TRUSTED_ENTRA_PRINCIPALS
    AGENT_CONSENT_HASH: AGENT_CONSENT_HASH
capability:
  consumes:
  - type: http
    namespace: azure-apim
    baseUri: https://management.azure.com
    description: Azure APIM Management API. Uses the standard
      /subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{svc}
      base path with the api-version=2023-09-01-preview query parameter.
    resources:
    - name: apim-users
      path: /subscriptions/{azureSubId}/resourceGroups/{rgName}/providers/Microsoft.ApiManagement/service/{serviceName}/users/{userId}
      operations:
      - name: createorupdateuser
        method: PUT
        description: Create a User entity (the agent's identity in APIM).
        outputRawFormat: json
        outputParameters:
        - { name: result, type: object, value: $. }
        inputParameters:
        - { name: body, in: body, type: object, required: true }
    - name: apim-products
      path: /subscriptions/{azureSubId}/resourceGroups/{rgName}/providers/Microsoft.ApiManagement/service/{serviceName}/products
      operations:
      - name: listproducts
        method: GET
        description: List existing Products to resolve the scope-tier name.
        outputRawFormat: json
        outputParameters:
        - { name: result, type: object, value: $. }
      - name: createorupdateproduct
        method: PUT
        description: Create a Product if the scope tier is missing. Product carries the
          quota, rate limit, and which APIs are accessible.
        outputRawFormat: json
        outputParameters:
        - { name: result, type: object, value: $. }
    - name: apim-subscriptions
      path: /subscriptions/{azureSubId}/resourceGroups/{rgName}/providers/Microsoft.ApiManagement/service/{serviceName}/subscriptions/{sid}
      operations:
      - name: createorupdatesubscription
        method: PUT
        description: Create the Subscription that grants the User access to the Product.
          The subscription holds two keys (primary + secondary); rotating one captures the
          credential value.
        outputRawFormat: json
        outputParameters:
        - { name: result, type: object, value: $. }
        inputParameters:
        - { name: body, in: body, type: object, required: true }
      - name: deletesubscription
        method: DELETE
        description: Revoke by deleting the Subscription.
        outputRawFormat: json
        outputParameters:
        - { name: result, type: object, value: $. }
    - name: apim-subscriptions-listSecrets
      path: /subscriptions/{azureSubId}/resourceGroups/{rgName}/providers/Microsoft.ApiManagement/service/{serviceName}/subscriptions/{sid}/listSecrets
      operations:
      - name: listsubscriptionsecrets
        method: POST
        description: Retrieve the subscription's primary + secondary key values.
        outputRawFormat: json
        outputParameters:
        - { name: result, type: object, value: $. }
    authentication:
      type: bearer
      token: '{{env.AZURE_BEARER_TOKEN}}'
  - type: http
    namespace: azure-application-insights
    baseUri: https://api.applicationinsights.io/v1
    description: Application Insights / Log Analytics — audit side-channel where Azure
      APIM diagnostic logs land. Custom dimensions on APIM requests carry the Web Bot Auth
      signature and consent hash for queryable observability.
    resources:
    - name: workspace-query
      path: /workspaces/{workspaceId}/query
      operations:
      - name: querylogs
        method: POST
        description: Kusto query against the APIM logs surface for audit observation.
        outputRawFormat: json
        outputParameters:
        - { name: result, type: object, value: $. }
        inputParameters:
        - { name: workspaceId, in: path, type: string, required: true }
        - { name: body, in: body, type: object, required: true }
    authentication:
      type: bearer
      token: '{{env.AZURE_BEARER_TOKEN}}'
  orchestration:
  - name: onboard-agent
    description: Verify identity (Web Bot Auth or Entra service principal), ensure
      Product, create User + Subscription, capture key from listSecrets, observe audit
      via App Insights.
    inputs:
    - { name: signature, type: object, required: false, description: 'Web Bot Auth signature for third-party agents.' }
    - { name: signature_agent, type: string, required: false }
    - { name: entra_principal, type: string, required: false, description: 'Microsoft Entra service principal object ID for first-party agents.' }
    - { name: skill_id, type: string, required: true }
    - { name: requested_scopes, type: array, required: true }
    - { name: consent_hash, type: string, required: true }
    - { name: contact, type: object, required: true }
    steps:
    - id: verify_identity
      type: builtin.identity.resolve
      description: Prefer Entra principal short-circuit for in-tenant first-party agents;
        fall back to Web Bot Auth for third-party.
      with:
        entra_principal: '${input.entra_principal}'
        trusted_entra_principals: '{{env.AGENT_TRUSTED_ENTRA_PRINCIPALS}}'
        signature: '${input.signature}'
        signature_agent: '${input.signature_agent}'
        trusted_issuers: '{{env.AGENT_TRUSTED_ISSUERS}}'
      on_failure: deny
    - id: verify_consent
      type: builtin.policy.assert
      with:
        assert: '${input.consent_hash} == {{env.AGENT_CONSENT_HASH}}'
      on_failure: deny
    - id: classify_scopes
      type: builtin.policy.scope-classify
      with:
        requested: '${input.requested_scopes}'
    - id: ensure_product
      type: builtin.upsert
      description: Ensure the scope-tier Product exists.
      with:
        list_call: azure-apim.listproducts
        filter: 'name == "${steps.classify_scopes.target}"'
        create_call: azure-apim.createorupdateproduct
        create_with:
          body:
            properties:
              displayName: '${steps.classify_scopes.target}'
              subscriptionRequired: true
              approvalRequired: false
              state: published
    - id: create_user
      call: azure-apim.createorupdateuser
      with:
        userId: 'agent-${steps.verify_identity.agent_id}'
        body:
          properties:
            firstName: Agent
            lastName: '${steps.verify_identity.agent_id}'
            email: 'agent-${steps.verify_identity.agent_id}@${input.contact.operator}'
            state: active
            note: 'Auto-onboarded by Naftiko capability for ${input.contact.operator}'
    - id: create_subscription
      call: azure-apim.createorupdatesubscription
      with:
        sid: 'agent-sub-${steps.verify_identity.agent_id}'
        body:
          properties:
            displayName: 'Agent ${steps.verify_identity.agent_id}'
            scope: '${steps.ensure_product.id}'
            ownerId: '${steps.create_user.id}'
            state: active
    - id: capture_credential
      call: azure-apim.listsubscriptionsecrets
      description: listSecrets is the only retrieval path for the subscription's primary key.
      with:
        sid: '${steps.create_subscription.name}'
    - id: emit_audit
      description: Azure Monitor captures the APIM management ops automatically;
        the orchestration writes a corresponding Application Insights custom event
        with the agent identity + Web Bot Auth signature + consent hash so the worker
        can query a single log surface.
      type: builtin.audit.emit
      with:
        target: azure-application-insights.querylogs
        custom_event:
          event_type: agent.onboarded
          agent_id: '${steps.verify_identity.agent_id}'
          trust_mode: '${steps.verify_identity.trust_mode}'
          operator: '${input.contact.operator}'
          skill_id: '${input.skill_id}'
          scope_tier: '${steps.classify_scopes.target}'
          consent_hash: '${input.consent_hash}'
          subscription_id: '${steps.create_subscription.id}'
    output:
      agent_id: '${steps.verify_identity.agent_id}'
      trust_mode: '${steps.verify_identity.trust_mode}'
      user_id: '${steps.create_user.id}'
      subscription_id: '${steps.create_subscription.id}'
      credential:
        type: SubscriptionKey
        header: Ocp-Apim-Subscription-Key
        value: '${steps.capture_credential.primaryKey}'
        revocation_url: '/v1/agents/${steps.verify_identity.agent_id}/revoke'
      scope_tier: '${steps.classify_scopes.target}'
  - name: revoke-agent
    description: Delete the Subscription; the User entity persists for audit purposes.
    inputs:
    - { name: subscription_id, type: string, required: true }
    steps:
    - id: delete_sub
      call: azure-apim.deletesubscription
      with:
        sid: '${input.subscription_id}'
    output:
      revoked: true
  exposes:
  - type: rest
    namespace: azure-apim-agent-onboarding-rest
    port: 8080
    resources:
    - path: /v1/agents/onboard
      operations:
      - { method: POST, name: onboardagent, call: orchestration.onboard-agent }
    - path: /v1/agents/{agent_id}/revoke
      operations:
      - { method: POST, name: revokeagent, call: orchestration.revoke-agent }
  - type: mcp
    namespace: azure-apim-agent-onboarding-mcp
    port: 9090
    transport: http
    tools:
    - name: agent-register
      hints: { readOnly: false, destructive: false, idempotent: false }
      call: orchestration.onboard-agent
    - name: agent-revoke
      hints: { readOnly: false, destructive: true, idempotent: true }
      call: orchestration.revoke-agent
  - type: agent-skill
    namespace: azure-apim-agent-onboarding-skill
    description: 'Agent skill at /skills/onboard-agent.md. Azure-specific addendum: agents
      running in-tenant (Azure Functions, App Service, Container Apps, Bedrock-equivalents)
      can present a Microsoft Entra service principal token and skip Web Bot Auth.'
    skill:
      name: onboard-agent
      file: skills/onboard-agent.md