The Microsoft 365 Copilot Retrieval API: What Every Developer Must Know Before Building a RAG Integration

You have organizational content spread across SharePoint sites and OneDrive for Business, and a stakeholder who wants your application to answer questions about it — accurately, and without asking employees to paste documents into a chat box one at a time. The obvious path is a retrieval-augmented generation (RAG) pipeline: crawl the content, chunk it, embed it, load a vector store, and keep all of it in sync as documents change. That is where the sprint estimate quietly triples.

The Microsoft 365 Copilot Retrieval API removes that layer entirely. Microsoft 365 already maintains a semantic index across SharePoint, OneDrive, and Copilot connectors, and the Retrieval API exposes that index directly through a single POST call. Before you scope a custom pipeline, the question worth answering is whether this endpoint already covers your scenario — and the honest answer depends on constraints that overview-level coverage tends to skip.

The chunking pipeline you have to keep running.

A production RAG stack needs a chunking strategy, a job that re-chunks changed documents, and error handling for the file types that break your parser. That is a service you now own, monitor, and page someone about at 2 a.m.

The embedding model you have to pay for.

Every document and every query has to be embedded. That is a recurring inference bill and a model-versioning problem — re-embed the entire corpus every time you change the model, or live with a mixed-vintage index.

The vector store you have to operate.

A vector database is another stateful system to provision, secure, back up, and scale. It is infrastructure that exists only to answer a question Microsoft 365 can already answer about its own content.

The sync job that drifts out of date.

Your index reflects the corpus as of the last crawl. Between crawls, it is stale. Users notice when your application cites a policy that was superseded last Tuesday, and closing that gap means crawling more often, which costs more.

The permission trimming you have to reimplement.

Enterprise content is access-controlled. A correct RAG pipeline must never surface a document to a user who cannot open it in SharePoint. That means replicating SharePoint’s permission model in your retrieval layer — and keeping it accurate as group memberships change.

The sensitivity labels and compliance controls you have to honor.

Information barriers, sensitivity labels, and retention are not optional in a regulated tenant. Rebuilding those enforcement points outside Microsoft 365 is both hard and a compliance liability if you get it wrong.

After reading this post, you will be able to:

  • ✅ Decide whether the Retrieval API fits your integration pattern before writing any code
  • ✅ Construct a valid request and read every field the response returns
  • ✅ Design a rate-limit and batching strategy around the 200-request-per-user-per-hour ceiling
  • ✅ Choose correctly between the Retrieval API and the Copilot Search API for your scenario

The Infrastructure You Do Not Have to Build

The Retrieval API returns relevant text extracts — chunks — from SharePoint, OneDrive for Business, and Copilot connector content that the calling user already has access to, optimized for context recall rather than document discovery [Ref 1]. It runs query transformations through the Microsoft 365 Copilot stack to improve semantic relevance over basic lexical matching, accepts natural language queries, and supports KQL-based filtering to narrow the scope [Ref 1]. The content never leaves Microsoft 365 — there is no egress and no separate index to maintain [Ref 1].

That last point is the architectural argument. The six pipeline problems from the opening — chunking, embeddings, the vector store, sync, permission trimming, and compliance enforcement — are all handled inside the semantic index that Microsoft 365 already maintains for Copilot itself. Permission trimming, sensitivity labels, and information barriers are enforced on every request as a property of the platform, not as code you write [Ref 5].

💡 Insight: The Microsoft 365 semantic index is maintained by Microsoft as part of the Copilot service. There is no crawl to schedule, no embedding job to run, and no “last indexed” timestamp for you to manage — the freshness problem that dominates a self-built RAG pipeline is not yours to solve.

What the index covers is worth knowing precisely, because coverage is not uniform across file types. Semantic (hybrid) retrieval is supported only for .doc, .docx, .pptx, .pdf, .aspx, and .one files; every other extension falls back to lexical retrieval only [Ref 1]. Text inside tables is extracted only from .doc, .docx, and .pptx files, and non-textual content such as images and charts is not retrieved at all [Ref 1]. File size ceilings apply: .docx, .pptx, and .pdf files larger than 512 MB are not supported, and all other extensions are capped at 150 MB [Ref 1]. The API inherits every limitation of the underlying semantic index [Ref 8].

One boundary applies absolutely, and it consistently surfaces at deployment rather than design time: the Retrieval API is available on the Global service only. GCC High, DoD, and China (21Vianet) are not supported [Ref 2]. If your tenant runs in a US Government or sovereign cloud, this API is not an option today, and no request shape changes that.

What this means for you: if your grounding corpus is Office documents, PDFs, and SharePoint pages in a Global-cloud tenant, the index already exists — but confirm your file types and cloud before you commit the design.


The One Gate You Must Clear Before the First Call: Delegated Auth

The Retrieval API uses the same authentication and authorization model as Microsoft Graph — standard OAuth 2.0 through Microsoft Entra ID, with tokens acquired via MSAL or any OIDC-compatible library through your existing Graph auth flow [Ref 5]. The consequential detail is not how you authenticate, but which permission types the API will accept.

Application permissions are not supported. This is stated plainly in the reference documentation, and it is the single most important architectural fact in this post [Ref 2]. Personal Microsoft accounts are not supported either [Ref 2]. The security documentation is explicit: “The Retrieval API supports delegated permissions only. With delegated permissions, Entra ID authenticates both the calling application and the signed-in user, and the API enforces the signed-in user’s permissions on every request.” [Ref 5]

Delegated-only is not a configuration preference — it is a gate that decides whether an entire class of integration can use this API at all. Every call must carry the identity of a signed-in user. That rules out daemon services, background job queues, scheduled sync workers, and any server-side automation that runs without a user in the loop. A nightly process that pre-computes answers, or a message-queue consumer with no user context, cannot use this API. You need an interactive flow that keeps a valid user token available for each request. Post 02 covers the delegated versus application permission mechanics in depth; the takeaway here is that the choice is made for you.

The reason this design exists is also the reason it is defensible: because every request runs as the signed-in user, permission trimming is automatic. The API filters results to content the signed-in user can access, conditional access policies in the tenant apply without additional developer configuration, and the platform enforces sensitivity labels — restricted content is never returned to a user who lacks access [Ref 5]. You get correct, per-user security enforcement precisely because there is no application-identity shortcut.

The minimum delegated scopes depend on which content you are reaching:

Permission scopeTypeContent source it unlocks
Files.Read.AllDelegatedSharePoint and OneDrive (required)
Sites.Read.AllDelegatedSharePoint and OneDrive (required)
ExternalItem.Read.AllDelegatedCopilot connector content

For SharePoint and OneDrive retrieval, Files.Read.All and Sites.Read.All are both required — this is not an either/or choice [Ref 2]. To retrieve Copilot connector content, add ExternalItem.Read.All [Ref 2]. Post 10 covers how external content is made available to the index through connectors and the externalItem data source.

What this means for you: confirm your application runs in an interactive user context before anything else — if it does not, the rest of this post describes an API you cannot use as it stands.


Constructing the Request

The endpoint is a single operation:

POST https://graph.microsoft.com/v1.0/copilot/retrieval HTTP/1.1
Authorization: Bearer {delegated-user-token}
Content-Type: application/json
{
"queryString": "What is our data classification policy for customer records?",
"dataSource": "sharePoint",
"filterExpression": "path:\"https://contoso.sharepoint.com/sites/InfoSec\"",
"maximumNumberOfResults": 25
}

The same operation is also available at /beta/copilot/retrieval, but the beta endpoint carries the standard caveat that beta APIs are subject to change and are not supported in production — use /v1.0 for anything you intend to ship [Ref 2].

The request body has six parameters — two required, four optional [Ref 2]:

ParameterTypeRequiredNotes
queryStringStringRequiredNatural language query; single sentence recommended; maximum 1,500 characters
dataSourceStringRequiredOne of sharePoint, oneDriveBusiness, externalItem — one data source per call
filterExpressionStringOptionalKQL expression to scope retrieval; silent failure if malformed
resourceMetadataString collectionOptionalMetadata fields to include per result; none returned by default
maximumNumberOfResultsInt32OptionalRange 1–25; default 25
dataSourceConfigurationObjectOptionalApplies to externalItem only; restricts retrieval to specific connector connectionId values

For queryString, provide as much context as possible and keep it to a single sentence — generic queries return generic results, and Microsoft’s guidance is to avoid spelling errors in context-rich keywords because they degrade semantic matching [Ref 1]. The 1,500-character ceiling is generous enough for a full natural-language question but not for stuffing an entire document in as context [Ref 2].

dataSource accepts exactly one value per call, and interleaved results across sources in a single request are not supported [Ref 1]. Note that OneDrive is addressed as oneDriveBusiness (work or school accounts only) — personal OneDrive does not apply [Ref 1].

💡 Insight: One data source per call is not a limitation to work around — it reflects how the semantic index is partitioned. If your scenario needs results from SharePoint and connectors together, issue separate calls and merge the hits in your application layer rather than expecting the API to interleave them.

The maximumNumberOfResults parameter ranges from 1 to 25 and defaults to 25. Microsoft’s explicit recommendation is not to limit it unless strict LLM token constraints force you to — a lower cap discards context the model might need [Ref 1][Ref 2].

The filterExpression parameter is where a production bug hides. It takes a KQL expression that scopes retrieval to matching content, and for SharePoint and OneDrive it supports these ten properties: Author, FileExtension, Filename, FileType, InformationProtectionLabelId, LastModifiedTime, ModifiedBy, Path, SiteID, and Title [Ref 2]. When you filter on path, use the path from the SharePoint or OneDrive Details pane — not a sharing link or a browser URL, which will not match [Ref 1].

Note: Incorrect KQL syntax in filterExpression does not raise an error. A malformed expression silently executes with no scoping applied, and the call returns unfiltered results as if you had passed no filter at all [Ref 2]. Test every filter expression against known content before you ship it — a scoping filter that silently does nothing is a correctness and data-exposure risk, not merely a performance one.

What this means for you: the request is small, but two defaults carry weight — leave maximumNumberOfResults at 25 unless tokens force otherwise, and never trust a filterExpression you have not verified returns the scoped set you expect.


Reading the Response: Extracts, Scores, and Labels

A successful call returns 200 OK with a retrievalResponse object whose core is the retrievalHits array [Ref 2]. An empty array is a valid, meaningful response — it means no relevant results were found, not that the call failed, and your handler must treat it as a first-class case rather than an error [Ref 2].

HTTP/1.1 200 OK
Content-Type: application/json
{
"retrievalHits": [
{
"webUrl": "https://contoso.sharepoint.com/sites/InfoSec/Shared%20Documents/DataClassification.docx",
"resourceType": "listItem",
"extracts": [
{
"text": "Customer records are classified as Confidential and must be stored only in approved repositories...",
"relevanceScore": 0.87
}
],
"sensitivityLabel": {
"labelId": "b7c9f3a1-...",
"displayName": "Confidential",
"priority": 3
}
}
]
}

Each hit carries the fields your grounding logic consumes [Ref 2]:

FieldWhat it holds
webUrlURL of the source document — use it for citations back to the original
extractsArray of text chunks, each with text and a relevanceScore
relevanceScoreCosine similarity, normalized 0–1; may be absent for Copilot connector items
resourceTypelistItem for SharePoint/OneDrive, externalItem for connectors
resourceMetadataRequested metadata fields as key-value pairs (only if you asked for them)
sensitivityLabelLabel details for SharePoint/OneDrive items: labelId, displayName, tooltip, priority, color

Two response behaviors change how you should build the prompt that consumes these hits.

First, the results are unordered. The API provides a relevanceScore per extract, but it does not rank or sort the hits it returns [Ref 1][Ref 2]. Microsoft’s guidance is to send all returned extracts to the LLM rather than truncating or pre-filtering on score — the scores describe similarity, but discarding lower-scored extracts risks dropping context the model needed to answer correctly [Ref 1]. If you do need an order for display or token budgeting, sort in your own code; do not assume the array arrives ranked.

Second, relevanceScore may be absent for connector items. The score is populated for SharePoint and OneDrive content but is not guaranteed for Copilot connector results [Ref 2].

💡 Insight: Because relevanceScore can be missing on connector hits, any truncation or ranking logic that reads the score must treat it as optional. Code that assumes the field is always present will throw — or silently drop connector extracts — the first time it runs against externalItem data.

The response shape above is documented through the reference examples. The dedicated retrievalResponse resource schema is published separately.

What this means for you: build your response handler for the honest contract — an empty array is normal, results are unordered, and relevanceScore is optional on connector hits — then pass all extracts to the model rather than second-guessing the platform’s recall.


Designing Around the 200-Request Ceiling

The Retrieval API is throttled at 200 requests per user per hour [Ref 1]. This is a per-user ceiling, not a per-application one, which is a favorable design: it scales with your active user count rather than forcing every user’s traffic through one shared bucket. For an application with 1,000 active users, the theoretical ceiling is 200,000 calls per hour — but the real constraint is per individual user, so a single power user hammering the endpoint will hit the wall long before your tenant-wide volume looks high.

Three design levers keep you under the ceiling without building something brittle.

Batch related calls. The API supports $batch, and you can include up to 20 Retrieval API requests in a single batch call [Ref 2]. When a user interaction naturally produces several queries — one per data source, or several filtered variants — batching collapses the round trips.

Cache stable queries. Many grounding queries are repetitive and time-bounded: the same policy question, the same KQL-scoped corpus, asked by many users across a day. Caching the extracts for a stable queryString plus filterExpression pair, with an expiry that matches how fast the underlying content changes, removes a large fraction of calls before they ever reach the ceiling.

Handle 429s manually inside batches. Standard Microsoft Graph throttling applies — a throttled call returns 429 Too Many Requests with a Retry-After header telling you how long to wait [Ref 9]. The subtlety with batching is that individual sub-requests inside a $batch can return 429 within the batch response body, and the Graph SDK does not automatically retry those. You must inspect each sub-response and requeue the throttled ones yourself, honoring each sub-response’s Retry-After value. Post 03 covers the full throttling and retry pattern; the batch-specific requeue is the piece most likely to be missed.

✅ Quick win: Before writing retry code, validate your request shape and permission scopes in Graph Explorer. A single successful interactive call there confirms your consent and payload are correct — and if you hold a Copilot license, the semantic index is already built, so that first call can return real results with no setup on your side.

What this means for you: budget the 200-per-user-per-hour ceiling against your busiest single user, not your tenant total, and put caching and batch-level 429 handling in the first version rather than bolting them on after the first throttling incident.


When to Use Retrieval and When to Use Search

The Retrieval API and the Copilot Search API are frequently confused — both query Microsoft 365 content on behalf of a signed-in user. They solve different problems. Retrieval returns text chunks to ground an LLM; Search returns ranked document metadata for discovery. Choosing the wrong one means either feeding file listings to a model that needed prose, or asking a grounding API to power a search results page. The Copilot Search API is a separate beta Graph surface — it is not covered as its own post in this series, but the comparison below is enough to decide which one your scenario needs.

Decision tree starting with “What do you need back?” branching to Retrieval API for text extracts and Search API for ranked files, with data source and stability notes.
DimensionRetrieval APISearch API (Preview)
PurposeRAG grounding — text extracts to feed an LLMDocument discovery — ranked files for a user to browse
OutputText chunks (extracts) with relevance scoresFile metadata, previews, file URLs
Data sourcesSharePoint, OneDrive, Copilot connectorsOneDrive only (other sources not yet supported)
Stabilityv1.0 endpoint availableBeta only — not supported in production
Rate limit200 requests/user/hour200 requests/user/hour
Max results per call25100 (pageSize)
Result orderingUnorderedOrdered by relevance
KQL filter depthExtensive (10 properties)Path only
EndpointPOST /v1.0/copilot/retrieval/beta/copilot/search/... (beta)

Sources: Retrieval [Ref 1], Search [Ref 7].

The choice is direct. If you are building the grounding layer of an AI application — extracting relevant passages to place in a model’s context window — use Retrieval. If you are building a discovery experience where a person browses ranked files, use Search. The delegated-only permission model applies to both, so neither one rescues a daemon scenario. And stability matters: Retrieval is a v1.0 endpoint you can ship, while Search is beta and not supported in production, which for most teams settles the question on its own until Search graduates.

What this means for you: if the consumer of the result is a language model, reach for Retrieval; if the consumer is a human scanning a list, the beta Copilot Search API is designed for that job — note it is still beta and not yet supported in production.


Pre-Go-Live Checklist

Before the Retrieval API carries production traffic, confirm each of these. Most are decisions rather than switches — get them wrong and you discover it in production, not in the debugger.

Licensing

  • [ ] Copilot license path: Each calling user has a Microsoft 365 Copilot add-on license on top of an M365 E3 or E5 subscription (or equivalent). The API is free to use under this license [Ref 1][Ref 4].
  • [ ] Or the pay-as-you-go path: If you are using the preview consumption model instead, confirm its prerequisites and restrictions below.

Permissions

  • [ ] Delegated only: The app registration requests delegated permissions, not application permissions [Ref 2].
  • [ ] Scopes granted and admin-consented: Files.Read.All and Sites.Read.All for SharePoint/OneDrive; add ExternalItem.Read.All for connector content [Ref 2].
  • [ ] Interactive user context: Every code path that calls the API has a signed-in user token available — no background or daemon call paths [Ref 5].

Rate limit and reliability

  • [ ] Per-user budget: Expected peak per-user call volume stays under 200 requests/user/hour, with caching for stable queries [Ref 1].
  • [ ] Batch 429 handling: Sub-request 429 responses inside $batch calls are inspected and requeued using each Retry-After value [Ref 9].
  • [ ] Empty-array handling: The response handler treats an empty retrievalHits array as “no results,” not as an error [Ref 2].

Content and filters

  • [ ] File-type coverage checked: Your grounding corpus is in semantically indexed formats (.doc, .docx, .pptx, .pdf, .aspx, .one) and within size limits [Ref 1].
  • [ ] KQL filters tested: Every filterExpression is verified to return the scoped set — remember malformed KQL fails silently [Ref 2].

Cloud boundary

  • [ ] Global cloud confirmed: The tenant runs in the Global service. GCC High, DoD, and China (21Vianet) are not supported and have no workaround [Ref 2].

⚠️ Preview: The pay-as-you-go consumption path — for tenants without Copilot licenses for every user — is in preview, launched January 2026, and carries constraints the licensed path does not [Ref 3][Ref 6]. It costs $0.10 per API call, billed through an Azure subscription. It covers SharePoint and Copilot connectors only — OneDrive, as a user-level source, is not available via pay-as-you-go. Prerequisites: an Azure subscription (owner or contributor), M365 tenant admin access, and at least one Microsoft 365 Copilot license must exist in the tenant even to enable pay-as-you-go. Enablement is done in the M365 admin center under Copilot > Billing & usage > Pay-as-you-go, and propagation takes roughly two hours. No SLA applies to the pay-as-you-go preview — do not put a latency- or availability-sensitive workload on it [Ref 3].

Note: The v1.0 Retrieval endpoint is live and is not labeled preview in its reference documentation. Separately, the terms document that governs all Microsoft 365 Copilot APIs is titled “Microsoft 365 Copilot APIs Terms of Use (preview).” That is the name of the collective terms document, not a statement that the Retrieval endpoint itself is in preview — treat the endpoint as available at v1.0, with the overall Copilot APIs program governed by terms still labeled preview [Ref 4][Ref 11].

What this means for you: the checklist is short, but the licensing path, the delegated-only gate, and the Global-cloud boundary are the three that block a launch outright — verify them before you build, not at go-live.


Now What? Your Next Three Steps

  • Register your app with delegated scopes and make one test call — grant Files.Read.All and Sites.Read.All, sign in as a real user, and issue a single POST /v1.0/copilot/retrieval in Graph Explorer to confirm consent, payload, and results before you write application code.
  • Design your rate-limit and caching architecture — map your busiest single user against the 200-request-per-user-per-hour ceiling, add caching for stable query-plus-filter pairs, and write batch-level 429 requeue logic from the start.
  • Decide whether the beta Copilot Search API adds anything — if any part of your scenario is document discovery rather than LLM grounding, confirm whether the beta Search API fits before you stretch Retrieval to do a job it was not built for.

How to Navigate This Series

This series runs 13 posts across three phases, taking the Microsoft 365 Copilot API surface from governance foundations through the core developer APIs to agents and extensibility.

  • Phase 1 — Foundations and Governance (Posts 01–04): the API landscape, auth and permissions, rate limits and national cloud, and usage analytics.
  • Phase 2 — Building with Core APIs (Posts 05–09): the Package Management API, then one deep-dive per core API surface — starting here with Retrieval, then Chat, Meeting Insights, and Change Notifications.
  • Phase 3 — Agents and Extensibility (Posts 10–13): connectors, the declarative-versus-custom-engine agent decision, Copilot Studio, and integration patterns.

By role:

  • Developers building a grounding layer: → Post 01 → Post 02 (auth) → Post 03 (rate limits) → Post 06 (this post) → Post 07 (Chat API)
  • Architects evaluating build-versus-buy for RAG: → Post 01 → Post 06 → Post 10 (connectors) for external content
  • IT admins and deployment leads: → Post 01 → Posts 02–05, then skim Phase 2 for awareness

For the delegated-versus-application permission mechanics referenced throughout this post, see Post 02. For the full 429 and Retry-After throttling pattern, see Post 03. To make external content available to the Retrieval API through the externalItem data source, see Post 10.

The immediate next post is Post 07 — Microsoft 365 Copilot Chat API: Work IQ (GA) vs Graph Beta, covering programmatic conversational invocation of Copilot.


References

All claims in this post trace to the following official Microsoft documentation:


Leave a Reply