Share via

Purview DLP and Entra ID app inconsistent behavior

Nina Polshakova 0 Reputation points
2026-05-07T19:01:32.3233333+00:00

Hi, I'm testing out the guide in https://learn.microsoft.com/en-us/purview/developer/use-the-api with Purview DLP and an application registered in Microsoft Entra ID, and running into some inconsistent behavior.

I can hit the https://graph.microsoft.com/beta/me/dataSecurityAndGovernance/processContent endpoint and get an expected block/allow response:

{
  "@odata.context": "https://graph.microsoft.com/beta/$metadata#microsoft.graph.processContentResponse",
  "protectionScopeState": "modified",
  "policyActions": [
    {
      "@odata.type": "#microsoft.graph.restrictAccessAction",
      "action": "restrictAccess",
      "restrictionAction": "block"
    }
  ],
  "processingErrors": []
}

But sometimes, (randomly) the same exact request that worked before will result in a licensing error:

{
  "error": {
    "code": "Unauthorized",
    "message": "Authorization is failed with code: InsufficientServicePlans.",
    "details": [],
    "innerError": {
      "diagnosticInfo": {
        "servicePlanIds": null,
        "errorCode": "InsufficientServicePlans",
      },
    }
  }
}
Microsoft Security | Microsoft Purview
0 comments No comments

4 answers

Sort by: Most helpful
  1. Nina Polshakova 0 Reputation points
    2026-05-11T14:23:56.4833333+00:00

    For more context here is the request I'm testing with the delegated flow:

    ❯ curl -sS \
      -X POST "https://graph.microsoft.com/v1.0/me/dataSecurityAndGovernance/processContent" \
      -H "Authorization: Bearer $GRAPH_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Client-Request-Id: $CLIENT_REQUEST_ID" \
      -d '{
        "contentToProcess": {
          "contentEntries": [
            {
              "@odata.type": "microsoft.graph.processConversationMetadata",
              "identifier": "'"$IDENTIFIER"'",
              "content": {
                "@odata.type": "microsoft.graph.textContent",
                "data": "Payment details: credit card number 4111 1111 1111 1111"
              },
              "name": "CC Test Content",
              "correlationId": "'"$CORRELATION_ID"'",
              "sequenceNumber": 0,
              "isTruncated": false,
              "createdDateTime": "'"$NOW"'",
              "modifiedDateTime": "'"$NOW"'"
            }
          ],
          "activityMetadata": {
            "activity": "uploadText"
          },
          "deviceMetadata": {
            "deviceType": "Unmanaged"
          },
          "integratedAppMetadata": {
            "name": "test-purview",
            "version": "0.1"
          },
          "protectedAppMetadata": {
            "name": "test-purview",
            "version": "0.1",
            "applicationLocation": {
              "@odata.type": "microsoft.graph.policyLocationApplication",
              "value": "'"$CLIENT_ID"'"
            }
          }
        }
      }'
    

    Was this answer helpful?

    0 comments No comments

  2. Nina Polshakova 0 Reputation points
    2026-05-11T14:18:21.76+00:00

    Hi,

    So this issue is still happening on the v1.0 api as well:

    Sometimes the behavior is as expected:

    {
      "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.processContentResponse",
      "protectionScopeState": "modified",
      "policyActions": [
        {
          "@odata.type": "#microsoft.graph.restrictAccessAction",
          "action": "restrictAccess",
          "restrictionAction": "block"
        }
      ],
      "processingErrors": []
    }
    

    And then I will see intermittent Unauthorized failures on the same request:

    {
      "error": {
        "code": "Unauthorized",
        "message": "Authorization is failed with code: InsufficientServicePlans.",
        "details": [],
        "innerError": {
          "date": "2026-05-11T14:07:04",
          "code": "InsufficientServicePlans",
          "clientRequestId": "client-id",
          "diagnosticInfo": {
            "tenantId": "tenant-id",
            "appId": "app-id",
            "objectId": "obj-id",
            "servicePlanIds": null,
            "errorCode": "InsufficientServicePlans",
            "upn": "my-email"
          },
          "activityId": "activity-id",
          "request-id": "req-id",
          "client-request-id": "client-id"
        }
      }
    }
    

    I have confirmed:

    1. Confirm Your Authentication Flow. I am currently testing the delegated flow with a graph access token issued on behalf of the signed-in user
    2. Verify the User Has an E5/A5/G5 or Equivalent Purview DLP License -> I've verified the license is correct. I can access and edit the DLP policy and have (sometimes) successful behavior.
    3. Enable Pay-As-You-Go Billing - yes, this is enabled
    4. Check Your Token Cache and Environment - I am using the same token between calls. The same token used will randomly result in different behavior (no set ordering in the failures, will get license error, followed by successful response)
    5. Consider Using the v1.0 Stable Endpoint - Tested this as above, with no change in behavior
    6. Recent Service-Side Incidents - are these still happening then?

    Was this answer helpful?

    0 comments No comments

  3. SAI JAGADEESH KUDIPUDI 3,115 Reputation points Microsoft External Staff Moderator
    2026-05-08T00:52:52.8333333+00:00

    Hi Nina Polshakova,
    Thank you for reaching out — we completely understand how frustrating intermittent errors can be, especially when the same code works sometimes and fails other times. The InsufficientServicePlans error you're seeing from the Graph DLP processContent API is tied to licensing and authorization, and the "random" behavior most likely has a logical explanation. Let us walk you through what to check.

    1. Confirm Your Authentication Flow (Delegated vs. App-Only)

    The processContent API behaves differently depending on how you authenticate. If you're using a delegated flow, you should call /me/dataSecurityAndGovernance/processContent with a signed-in user context and the Content.Process.User (least privileged) or Content.Process.All delegated permission. If you're using an app-only flow, you cannot call the /me endpoint — you must use /users/{userId}/dataSecurityAndGovernance/processContent and grant the app the Content.Process.All application permission. Calling /me with an app-only token will fail because the /me endpoint requires a signed-in user context, and the service principal itself is not licensed for DLP — this will produce the InsufficientServicePlans error.

    1. Verify the User Has an E5/A5/G5 or Equivalent Purview DLP License

    The processContent API evaluates the caller's Purview/DLP service plan. The user (in delegated scenarios) or the target user (in app-only scenarios) must have one of the following licenses: Microsoft 365 E5/A5/G5, E5/A5/G5/F5 Compliance or F5 Security & Compliance add-on, or E5/A5/G5/F5 Information Protection & Governance add-on. You can decode your JWT token and inspect the scp (delegated) or roles (app-only) claim to confirm the correct permissions are present.

    1. Enable Pay-As-You-Go Billing — This Is a Critical Prerequisite

    This is easy to overlook but very important. Microsoft's official integration guide states that managing AI interactions with Microsoft Purview requires you to enable pay-as-you-go billing in your organization. If pay-as-you-go billing is not enabled, the processContent API may intermittently fail — this could very well be the root cause of the "random" behavior you're experiencing. Please verify this is enabled in the Microsoft Purview portal → Settings → Billing.

    1. Check Your Token Cache and Environment

    If your application's token cache sometimes returns tokens for a licensed user and sometimes for an unlicensed user (or switches between delegated and app-only tokens), that would perfectly explain the intermittent failures. Please make sure your token acquisition logic is consistent, you're not mixing user contexts across requests, and the token cache is not stale or serving tokens from different identities.

    1. Consider Using the v1.0 Stable Endpoint

    We noticed you're using the beta endpoint. The processContent API is now also available in v1.0 (graph.microsoft.com/v1.0/me/dataSecurityAndGovernance/processContent). For production workloads, we recommend switching to the v1.0 endpoint for better stability and support.

    1. Recent Service-Side Incidents

    It's also worth noting that there were transient service availability incidents on the ProcessContent API around early May 2026, where the API availability briefly dropped below SLA. These were self-mitigated on Microsoft's end. So some of the intermittent failures you experienced may not have been entirely on your side.

    To help us narrow things down further, could you please clarify:

    • Are you always using the same authentication flow (delegated vs. app-only)?
    • Which endpoint are you calling — /me/... or /users/{id}/...?
    • Can you share the decoded JWT (with PII removed) so we can verify the scp or roles claim?
    • Does the user(s) you're testing with definitely have an E5/A5/G5 or equivalent Purview DLP license assigned?
    • Is pay-as-you-go billing enabled in your Purview portal?

    The InsufficientServicePlans error is almost always caused by one of three things: a missing or mismatched DLP-capable license on the calling user, an app-only token accidentally hitting the /me endpoint, or pay-as-you-go billing not being enabled in your tenant. We strongly recommend starting by verifying your authentication flow and confirming the correct permission name (Content.Process.All or Content.Process.User), then checking that pay-as-you-go billing is active, and finally switching to the stable v1.0 endpoint for production use. Once these items are addressed, the intermittent failures should stop. If the issue persists after these checks, please share the decoded JWT and we'll investigate further.

    Microsoft Reference Links:

    Hope this helps. If you have any follow-up questions, please let me know. I would be happy to help.

    Was this answer helpful?


  4. Q&A Assist
    2026-05-07T19:02:03.7166667+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    The behavior matches how Microsoft Purview DLP policies are evaluated for Entra-registered apps and how the Microsoft Purview APIs are intended to be used.

    Key points from the documented flow:

    1. A valid Purview DLP policy must exist and be scoped to the Entra-registered app
      • For Entra-registered AI or custom apps, DLP policies must be created with the New-DlpComplianceRule PowerShell cmdlet and explicitly scoped to that app.
      • The Microsoft Purview portal does not currently support creating DLP policies for Entra-registered applications.
      • If the tenant or user does not have the required Purview/DLP service plans or if the policy is not correctly configured for that app, calls to the Purview APIs can fail with authorization or licensing-related errors.
    2. The app must follow the two-step pattern with protectionScopes/compute and processContent
      • Step 1: Call protectionScopes/compute for the user and cache the returned ETag header, which represents the current state of the protection scopes for that user.
      • Step 2: For each relevant user activity, call processContent and send the cached ETag in the If-None-Match header.
      • If policies change (for example, DLP configuration or licensing changes that affect which policies apply), protectionScopeState in the processContent response can be modified. In that case, the app must call protectionScopes/compute again to get updated scopes and a new ETag, then continue using that new ETag.
    3. Execution mode and policy changes affect how and when the app must call processContent
      • For activities like uploadText (prompts), the app must block the main thread and wait for processContent when executionMode is evaluateInline.
      • For other activities such as downloadText, the app can call processContent asynchronously.
      • The executionMode and protection scopes are derived from protectionScopes/compute. If the app is not consistently using the latest ETag or is skipping protectionScopes/compute after protectionScopeState: "modified", behavior can appear inconsistent.
    4. Licensing-related errors can appear when service plans or policies are not consistently available
      • The error InsufficientServicePlans indicates that, at the time of the call, the user or tenant did not meet the required service plan conditions for the Purview DLP features being invoked.
      • Because the APIs rely on the current protection scopes and policies, any change in licensing, policy configuration, or scope assignment that affects the user can cause some calls to succeed (when valid scopes and plans apply) and others to fail (when they do not).

    To stabilize behavior in line with the documented pattern:

    1. Ensure the tenant admin has:
      • Configured Microsoft Purview and created DLP policies via New-DlpComplianceRule specifically scoped to the Entra-registered app.
      • Verified that the required Purview/DLP service plans are assigned to the users being tested.
    2. Ensure the app:
      • Always calls protectionScopes/compute first and caches the ETag per user.
      • Sends the cached ETag in the If-None-Match header on every processContent call.
      • When processContent returns protectionScopeState: "modified", immediately calls protectionScopes/compute again, caches the new ETag, and uses it for subsequent processContent calls.
      • Honors the executionMode guidance: block inline for uploadText, call asynchronously for downloadText, and follow the documented behavior for evaluateInline and evaluateOffline.
    3. For AI apps specifically:
      • If using Endpoint DLP or AI-app-specific DLP rules, ensure the PowerShell-based DLP rule configuration for the Entra-registered AI app is in place and that the app is integrated with the Microsoft Purview APIs as described.

    Following this pattern ensures that policy and licensing changes are consistently reflected via protectionScopes/compute and ETag, reducing inconsistent processContent outcomes, including intermittent InsufficientServicePlans errors.


    References:

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.