Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 9 min read Last reviewed Jul 2026

Microsoft Teams Error Guide: Graph JSON Batch 'Number of requests exceeds maximum' — Chunk to 20

Quick answer

Fix Microsoft Graph JSON batch errors in Teams automation: keep batches to 20 requests, check each sub-response, order dependent calls, and retry only failures.

  • #microsoft-teams
  • #adaptive-cards
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Microsoft Teams error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

The Microsoft Graph $batch endpoint rejects a JSON batch that contains more than 20 individual requests. When your Teams provisioning or bulk-messaging code packs too many operations into one batch, Graph returns a 400 before running any of them.

{
  "error": {
    "code": "BadRequest",
    "message": "Number of requests (34) exceeds the maximum allowed (20).",
    "innerError": { "date": "2026-07-09T10:12:44", "request-id": "b1e2..." }
  }
}

A subtler failure: the batch returns HTTP 200 overall, but individual sub-responses carry 429/403/404 that your code ignores.

Symptoms

  • Bulk Teams operations (add many members, create many channels, send many chat messages) fail entirely with a 400 batch error.
  • The batch call returns 200 but some operations silently didn’t happen.
  • Sub-requests that depend on an earlier one (create channel, then post to it) fail with 404 because they ran out of order.
  • Intermittent 429s appear inside sub-responses under load even though the batch itself “succeeded”.
  • A single bad sub-request appears to poison the perceived success of the whole batch.

Common Root Causes

  • Over-20 batch — more than 20 requests in one $batch body.
  • Ignoring sub-status — treating the batch’s outer 200 as success without checking each responses[].status.
  • Missing dependency ordering — not using dependsOn for requests that must run sequentially.
  • No per-request throttle handling — a sub-response 429 with Retry-After isn’t retried.
  • Non-idempotent retries — retrying an entire batch (including already-succeeded ops) on partial failure, causing duplicates.

Diagnostic Workflow

A valid batch keeps requests at 20 or fewer, with explicit ids and dependsOn where needed:

curl -X POST "https://graph.microsoft.com/v1.0/\$batch" \
  -H "Authorization: Bearer ${T}" -H "Content-Type: application/json" \
  -d '{
    "requests": [
      { "id": "1", "method": "POST",
        "url": "/teams/{team-id}/channels",
        "headers": { "Content-Type": "application/json" },
        "body": { "displayName": "incident-4821", "membershipType": "standard" } },
      { "id": "2", "method": "POST", "dependsOn": ["1"],
        "url": "/teams/{team-id}/channels/{channel-id}/messages",
        "headers": { "Content-Type": "application/json" },
        "body": { "body": { "content": "War room opened" } } }
    ]
  }'

Always iterate the sub-responses and act on each status:

{
  "responses": [
    { "id": "1", "status": 201, "body": { "id": "19:abc..." } },
    { "id": "2", "status": 429, "headers": { "Retry-After": "12" },
      "body": { "error": { "code": "TooManyRequests" } } }
  ]
}

Chunk a large workload into batches of 20 in code:

# Pseudocode: split 34 ops into 20 + 14
for chunk in $(split_into_batches ops.json 20); do
  post_batch "$chunk"
  handle_sub_429s_and_retry "$chunk"
done

Example Root Cause Analysis

A tenant-onboarding tool provisioned a Team per new customer: it created the team, added 25 members, and created 8 channels — packing all 34 Graph calls into one $batch. It failed with “Number of requests (34) exceeds the maximum allowed (20)” and, because the code only checked the outer HTTP status, logged a confusing 400 with no per-operation detail.

The rewrite chunked operations into batches of 20, used dependsOn so channel creation waited for team creation, and inspected every responses[].status. It retried only the sub-requests that returned 429 (respecting Retry-After) instead of resubmitting the whole batch, which had previously created duplicate channels on retry. Onboarding went from flaky to deterministic, and partial failures became individually retriable instead of an all-or-nothing 400.

Prevention Best Practices

  • Cap every JSON batch at 20 requests; chunk larger workloads in code.
  • Inspect each responses[].status — never treat the outer 200 as blanket success.
  • Use dependsOn for ordered operations (create-then-use) so sequential dependencies hold.
  • Retry only the failed sub-requests, honoring per-response Retry-After, and make operations idempotent.
  • Add jitter/backoff across chunks to avoid self-inflicted throttling on bulk runs.
  • Log per-request outcomes with their id so partial failures are diagnosable.

Quick Command Reference

# Count requests before posting a batch
jq '.requests | length' batch.json   # must be <= 20

# Post the batch and pretty-print sub-responses
curl -s -X POST 'https://graph.microsoft.com/v1.0/$batch' \
  -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
  -d @batch.json | jq '.responses[] | {id, status}'

# List only failed sub-requests
curl -s ... | jq '.responses[] | select(.status >= 400)'

Conclusion

Graph JSON batching fails loudly at the 20-request ceiling and fails quietly when you ignore per-request statuses. For reliable Teams bulk automation, chunk to 20, order dependent calls with dependsOn, check every sub-response, and retry only what actually failed — idempotently and respecting Retry-After. That turns brittle all-or-nothing batches into predictable, individually recoverable operations.

Free download · 368-page PDF

Fixed it? Get 500 Microsoft Teams & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.