If your project data lives in ten different places across Microsoft 365, how do you get a single, clear status update without wasting half your day clicking around? Imagine asking Copilot, "Where are we on Project Apollo?" and getting one accurate answer instantly—no spreadsheets, no manual reports. Today, I’ll show you exactly how to build the plugin that makes that possible… starting from zero.You’ll see the real API calls, the manifest that tells Copilot what to do, and how to wire up secure access. The result? One question in, one answer out—every time.Where Project Data Hides in Microsoft 365Most project managers think they know exactly where their updates live—until that dreaded Friday afternoon report request lands. You start pulling together the numbers and suddenly you’re digging through tools you haven’t opened in weeks. Tasks hiding in Planner. Milestones buried in a SharePoint list someone swore they’d keep updated. Conversations in Teams channels that contain half the context for why a deadline just slipped. It’s all there somewhere; it’s just not in one place, and it’s definitely not talking to each other without your help. Within Microsoft 365, project data scatters itself more than most people expect. Planner is great for action items—dates, assignments, checklists—but it doesn’t store client approvals. That’s often handled in a SharePoint list, maybe with a Power Automate workflow wrapped around it. Meanwhile, the real discussions about resource changes or scope shifts are happening inside a Teams channel, where the chat lives in an entirely different data store. Each of these tools thrives in its own lane, and none of them are naturally built to merge their information streams without extra work. The problem is simple enough to describe, but painful to live with. Your manager, or maybe your client, doesn’t ask for a Planner view, a SharePoint table, and a Teams transcript. They ask for an answer: “How’s the project going?” But behind that question is a mess of API structures, each with their own way of representing and delivering data. Planner’s API wraps data in nested objects you have to unwrap. SharePoint’s REST endpoints demand list IDs and column names you have to know ahead of time. And Teams? Threads, replies, reactions—all formatted differently again. Picture a typical project. The development tasks are tracked in Planner buckets. Every milestone approval—design sign-off, budget confirmation—is stored in a SharePoint list maintained by the PMO. Resource allocation discussions are in Teams messages, often with key details like “John can’t join next sprint” buried three replies deep. When a stakeholder asks for a status update, you’re either exporting data from three interfaces or manually piecing it together in Excel. By the time you finish, you can’t be sure all of it is even current. That’s where the risk kicks in. Manual reporting isn’t just slow—it raises the chance that outdated or inaccurate information slips through. Maybe a planner task got marked complete fifteen minutes ago, but you pulled data an hour earlier. Or an approval got logged in SharePoint after you’d already snapshot the list. Inconsistent timestamps, different refresh behaviors, and mismatched field names mean you’re spending more time reconciling the sources than analyzing anything. One thing that surprises a lot of people: even inside the same Microsoft 365 environment, these services don’t share a single authentication model or query syntax. Some endpoints work fine with delegated permissions; others demand application-level permissions with admin consent. Filter parameters can vary from OData queries in Graph to CAML-style conditions for certain SharePoint operations. You’re constantly switching mental gears just to talk to the data you own. You might think, “I’ll just plug it all into Power BI.” And yes, that can help with visualization after you’ve done the heavy lifting. But the real win would be making Copilot itself capable of pulling from Planner, SharePoint, and Teams directly—without you acting as the middleman. That means teaching it the exact endpoints, parameters, and authentication flows each source requires, so you can ask a natural language question and actually get a complete answer back. Step one in that process is deceptively simple: knowing exactly which services are holding the information you care about. Once you can point to the right containers—Planner for tasks, SharePoint for approvals, Teams for context—you’re ready to move past screenshots and spreadsheets. In the next stage, we’ll take that map and turn it into actual API calls that Copilot can run on its own. That’s when the scattered pieces finally start to connect.Mapping the APIs That MatterKnowing where your project data lives is only half the battle. The real challenge is getting it out in a clean, usable format that Copilot can consume without choking on it. Planner and SharePoint may look friendly in the browser, but the moment you start pulling data programmatically, you hit the reality that each one speaks a slightly different language. This is where we narrow the field to the two main gateways we need to master: Microsoft Graph for most of the M365 ecosystem and the SharePoint REST API for anything living deep inside lists and document libraries. On paper, Microsoft Graph is straightforward. You make a request to an endpoint like `/planner/tasks` and it hands you back task data. In reality, that “task data” is wrapped in multiple levels of JSON objects that you need to unwrap just to get a title, due date, and assigned user. Properties like `bucketId` or `planId` are opaque until you’ve run a separate query to resolve them. Contrast that with SharePoint’s REST API, which doesn’t give you a global feed of items at all. You have to know the exact list you want, right down to its internal GUID, and then structure your call as `/sites/{siteId}/lists/{listId}/items`. If that list has custom columns, you have to explicitly request those fields; otherwise, they never come back. Let’s take a real example. You might pull Planner tasks with Graph using something like: `GET /planner/plans/{planId}/tasks?$select=title,dueDateTime,assignments` That will get you the essentials, but you’ll still need follow-up calls to map user IDs to display names. Now compare that to milestones in SharePoint: `GET /sites/{siteId}/lists/{listId}/items?$select=Title,Status,DueDate` The verbs and the query options feel similar, but under the hood they behave differently. Graph honors OData querying rules for filtering and ordering, while SharePoint’s API can be picky about case sensitivity and internal column names. Once you start writing these calls, you have to think beyond just the syntax. Graph enforces per-app and per-user rate limits that can throttle your requests if you’re not careful. SharePoint endpoints might not hit you with the same quotas, but they will slow noticeably if you start returning thousands of rows for no reason. That’s why filtering at the source is critical. If you know you only need active tasks due in the next 14 days, it’s better to include that filter in the request itself than to pull everything and trim it later. And then there are authentication scopes to consider. For the Planner endpoint, you might need `Tasks.Read` at the delegated level. For a SharePoint list, you might be requesting `Sites.Read.All` or even a narrower, site-specific scope. Mix those up, and you’ll get mysterious 403 errors that look like your code is broken when it’s really just an under-scoped token. Think of it like using two different delivery companies to get parts for a single build. Both will eventually get the packages to you, but one labels their boxes with SKUs and the other just scribbles a description on the side. Until you open them and match the contents, you can’t start assembling anything. Copilot works the same way—it needs a consistent, predictable format to combine these data sets into something useful. The best move at this stage is to define your exact calls, with the filters and fields you truly need, and document them. That way, you’re not reinventing the wheel every time the plugin needs to run them. Copilot can’t guess these endpoints. It has to be told, in explicit terms, where to look and what to ask for. Once you have that list of precise API calls, you’ve essentially built a blueprint for how your plugin will fetch the right details at the right time. Next, we’ll turn that blueprint into something Copilot can actually read—the manifest that acts as the translator between these APIs and the natural language questions users will throw at it.Writing the Manifest That Teaches CopilotCopilot can’t see your APIs until you hand it a map. That map is the plugin manifest. Without it, Copilot has no idea where your data lives, what parameters it needs, or how to turn a vague request into a precise API call. The manifest is essentially a contract between your data and Copilot’s natural language layer. It says, “When a user asks about this kind of thing, here’s where you go and here’s how you ask for it.” At its core, the manifest is just a structured JSON file. It lists the endpoints your plugin can call, the methods they support, and the inputs they require. Each operation you define in the manifest has a description that tells Copilot—in plain language—what it does. You include parameters: their names, types, whether they’re required, and a short explanation of what they represent. It’s not enough to say “projectId.” You need to tell Copilot that projectId corresponds to a Planner bucketId or a SharePoint list filter so it can make the right connection when parsing user intent. Get a manifest entry wrong, and you’ll see two kinds of failure. In one case, Copilot might hit the wrong endpoint or pass the wrong parameter, serving up irrelevant results. In the other, it refuses to call your API entirely because the manifest and user
Become a supporter of this podcast: https://www.spreaker.com/podcast/m365-show-podcast--6704921/support.