graph.usergraph-appgraph-ts
purpose
Microsoft Graph API access via userGraph (delegated) and appGraph (app-level) clients with typed endpoint imports.
rules
- Import Graph endpoints from
@microsoft/teams.graph-endpointsfor v1.0 APIs and@microsoft/teams.graph-endpoints-betafor beta APIs. These are auto-generated typed functions, not raw URL strings. github.com/microsoft/teams.ts - Use
userGraphfor operations that act on behalf of the signed-in user (delegated permissions). This requires the user to have completed the OAuth sign-in flow (isSignedIn === true). learn.microsoft.com -- Delegated permissions - Use
appGraphfor operations that run under the application's own identity (application permissions). This does not require user sign-in but requires admin consent for the target tenant. learn.microsoft.com -- Application permissions - Call endpoints with
graph.call(endpoints.{resource}.{action}, params)whereparamsis an object containing path parameters, query parameters, and the request body. Path parameters use kebab-case keys matching the Graph URL template (e.g.,'chat-id'). github.com/microsoft/teams.ts - Use OData query parameters (
$top,$filter,$select,$orderby,$expand) as top-level keys in the params object to control response shape and size. Always set$topon list endpoints to avoid unbounded result sets. learn.microsoft.com -- OData query params - Endpoint names follow a consistent pattern:
endpoints.{resource}.getfor single-item GET,endpoints.{resource}.listfor collection GET,endpoints.{resource}.createfor POST,endpoints.{resource}.updatefor PATCH,endpoints.{resource}.deletefor DELETE. github.com/microsoft/teams.ts - Always wrap Graph calls in try/catch. Failed calls throw errors with HTTP status codes and Graph error details. Check for 401 (token expired), 403 (insufficient permissions), and 429 (throttled). learn.microsoft.com -- Error responses
- For nested resources, endpoints chain with dot notation:
endpoints.chats.messages.list,endpoints.chats.messages.create. Pass the parent resource ID as a path parameter (e.g.,'chat-id': chatId). github.com/microsoft/teams.ts - Prefer
$selectto retrieve only the fields you need. This reduces payload size and avoids retrieving sensitive data. For example,$select: 'displayName,mail'on a user query. learn.microsoft.com -- Select parameter - Never call
userGraphwithout first verifyingisSignedIn. CallinguserGraph.call()without a valid delegated token throws an authentication error. Gate alluserGraphusage behind the sign-in guard pattern. github.com/microsoft/teams.ts
patterns
Delegated user profile lookup
import { App } from '@microsoft/teams.apps';
import * as endpoints from '@microsoft/teams.graph-endpoints';
const app = new App({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
tenantId: process.env.TENANT_ID,
oauth: { defaultConnectionName: 'graph' },
});
app.on('message', async ({ isSignedIn, signin, userGraph, send }) => {
if (!isSignedIn) {
await signin({ signInButtonText: 'Sign In' });
return;
}
// GET /me -- delegated call using the signed-in user's token
const me = await userGraph.call(endpoints.me.get);
await send(`Hello ${me.displayName}! Your email is ${me.mail}.`);
});
app.start(3978);App-level user listing with query parameters
import { App } from '@microsoft/teams.apps';
import * as endpoints from '@microsoft/teams.graph-endpoints';
const app = new App({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
tenantId: process.env.TENANT_ID,
});
app.on('message', async ({ appGraph, send, activity }) => {
// GET /users -- app-level call, no user sign-in required
// Requires Application permission: User.Read.All with admin consent
const users = await appGraph.call(endpoints.users.list, {
$top: 10,
$filter: "department eq 'Engineering'",
$select: 'displayName,mail,department',
});
const names = users.value.map((u: any) => u.displayName).join(', ');
await send(`Engineering team: ${names}`);
});
app.start(3978);Sending a chat message via Graph
import { App } from '@microsoft/teams.apps';
import * as endpoints from '@microsoft/teams.graph-endpoints';
const app = new App({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
tenantId: process.env.TENANT_ID,
});
app.on('message', async ({ appGraph, send, activity }) => {
const chatId = activity.conversation.id;
try {
// POST /chats/{chat-id}/messages -- send a message to a chat
await appGraph.call(endpoints.chats.messages.create, {
'chat-id': chatId,
body: { content: 'Hello from the bot via Graph!' },
});
await send('Message sent via Graph API.');
} catch (err: any) {
if (err.status === 403) {
await send('Insufficient permissions to send chat messages.');
} else if (err.status === 429) {
await send('Throttled by Graph API. Please try again later.');
} else {
throw err;
}
}
});
app.start(3978);pitfalls
- Calling
userGraphwithout sign-in guard:userGraph.call()throws ifisSignedInisfalse. Always checkisSignedInfirst and callsignin()if needed. - Missing admin consent for app permissions:
appGraphcalls with application permissions (e.g.,User.Read.All) require an Azure AD admin to grant consent. Without it, calls return 403. - Unbounded list queries: Calling
endpoints.users.listwithout$topreturns a default page size but may trigger pagination. Always set$topto control result size. - Wrong path parameter key names: Graph endpoint path parameters use kebab-case (e.g.,
'chat-id','user-id'), not camelCase. A wrong key silently omits the parameter, producing a malformed URL. - Confusing v1.0 and beta endpoints: Importing from
@microsoft/teams.graph-endpointsgives v1.0 stable APIs. Beta endpoints from@microsoft/teams.graph-endpoints-betamay change without notice and should not be used in production. - Not handling throttling (429): Graph API enforces rate limits. A 429 response includes a
Retry-Afterheader. Ignoring it causes cascading failures. - Over-fetching data: Not using
$selectretrieves all properties, including potentially sensitive fields. Always scope queries to needed fields. - Using
appGraphfor user-specific data:appGraphhas no user context. Callingendpoints.me.getwithappGraphfails because/merequires delegated permissions.
references
- Microsoft Graph API overview
- Graph permissions overview
- Graph OData query parameters
- Graph error responses
- Graph API rate limiting
- Teams AI Library v2 -- GitHub
- @microsoft/teams.graph-endpoints npm
instructions
This expert covers Microsoft Graph API access in Teams bots built with the Teams AI Library v2 (@microsoft/teams.ts) in TypeScript. Use it when you need to:
- Call Graph endpoints using
userGraph(delegated, on behalf of a signed-in user) orappGraph(application-level, service-to-service) - Import and use typed endpoints from
@microsoft/teams.graph-endpointsor@microsoft/teams.graph-endpoints-beta - Understand the endpoint naming pattern (
endpoints.{resource}.{action}) - Pass path parameters, query parameters (
$top,$filter,$select), and request bodies - Handle Graph API errors (401, 403, 429) gracefully
Pair with auth.oauth-sso-ts.md for sign-in flow setup before using userGraph, and runtime.app-init-ts.md for App constructor configuration. Pair with auth.oauth-sso-ts.md for the sign-in flow that enables userGraph, and runtime.app-init-ts.md for App credential configuration.
research
Deep Research prompt:
"Write a micro expert on Microsoft Graph usage in Teams SDK v2 (TypeScript). Explain appGraph vs userGraph, required permissions/consent, calling generated endpoints from @microsoft/teams.graph-endpoints, OData query parameters, common endpoints (me, users, chats, messages), error handling patterns, and beta endpoint usage. Include 2-3 TypeScript code examples."