Skip to main content
Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE, client credentials, client-bound refresh tokens, RS256 ID tokens, OIDC discovery, and a stateful Outlook emulator on Microsoft Graph v1.0 and beta: the user directory, mail, calendar, contacts, Microsoft To Do, rooms and places, Microsoft 365 groups, and change notifications. SharePoint Online and OneDrive are served through Graph and the SharePoint REST API from one shared state (see SharePoint and OneDrive).

At a glance

Start

Microsoft listens on port 4005 in the default set, or on http://localhost:4000 when started alone. Sign-in and Graph v1.0 and beta share the port. Without a seed config there is one user ([email protected], Test User) in tenant 9188040d-6c67-4c5b-b112-36a304b66dad.

Using the Graph SDK

@microsoft/microsoft-graph-client attaches the token and follows @odata.nextLink only for https hosts. Start the emulator with an https base URL (it still listens on plain HTTP), list the host in customHosts, and end the middleware chain with a transport that sends each request to the HTTP listener:
Instead of the transport, you can also put any TLS reverse proxy in front of the emulator and pass its URL as --base-url. Plain fetch or curl against http://localhost:4005/v1.0/... needs none of this. MSAL Node takes the emulator as its authority (<base>/<tenant_id> with the host in knownAuthorities) and a custom system.networkClient that sends its requests to the HTTP listener; src/__tests__/graph-sdk.ts has a complete one. The Kiota based @microsoft/msgraph-sdk works with @azure/identity given authorityHost (the emulator) and disableInstanceDiscovery: true on the credential, with adapter.baseUrl set to <base>/v1.0. The package tests (graph-mail.sdk.test.ts, graph-users.sdk.test.ts, and graph-kiota.sdk.test.ts) run all three libraries against the emulator unchanged: client credentials and authorization code flows, a draft with an attachment, send, reply, delta, PageIterator, $batch, errors and MIME, and with the Kiota SDK a meeting invite and acceptance, calendarView with Prefer: outlook.timezone, getSchedule, contacts with a lambda filter, and a cancellation. The conformance scenarios in conformance/scenarios/sdk.ts go further: the Kiota SDK and the Graph JavaScript client run a mail workflow against a real Microsoft 365 test tenant, their traffic is recorded, and the emulator must reproduce every response.

Auth

  • Access tokens are RS256 JWTs from this emulator’s token endpoint. Authorization code and refresh tokens are delegated: their scp holds every Graph scope the user has consented to for the client, as in Entra ID, and https://graph.microsoft.com/.default grants the client’s delegated_permissions. Client credentials tokens are application tokens: they need a /.default scope (anything else is AADSTS1002012 invalid_scope) and carry roles from the client’s application_permissions. A client without either list gets every modeled permission.
  • Each operation checks the least-privileged Graph permission, scp for delegated tokens and roles for application tokens, with Graph’s error for each surface (403 Authorization_RequestDenied on the directory, 403 ErrorAccessDenied on Exchange). /me with an application token is 400 BadRequest (/me request is only valid with delegated authentication flow.).
  • A missing, expired, revoked, or foreign token is 401 InvalidAuthenticationToken with a WWW-Authenticate header. A token issued for another resource is Access token validation failure. Invalid audience.
  • Without graph.strict_auth: true, an opaque bearer token the emulator did not issue (any string such as Bearer test) acts as the first seeded user with full access, so quick scripts work without an OAuth flow. JWTs are always verified, and a request without a token is always 401.
  • A scope for another resource, such as https://contoso.sharepoint.com/.default, gives a token for that resource. SharePoint tokens carry aud: 00000003-0000-0ff1-ce00-000000000000 (the SharePoint app id), whether the scope names a tenant host or that id, as Entra issues them. Graph refuses them, and the SharePoint REST API takes only them.
  • Delegated directory writes (POST /users, PATCH or DELETE /users/{id} on someone else) need the permission only. The emulator does not model Entra admin roles.

OAuth and OIDC

  • GET /.well-known/openid-configuration — Get OIDC discovery document: for the root issuer
  • GET /:tenant/v2.0/.well-known/openid-configuration — Get tenant OIDC discovery document
  • GET /discovery/v2.0/keys — Get JSON Web Key Set (JWKS)
  • GET /:tenant/discovery/v2.0/keys — Get tenant JSON Web Key Set (JWKS)
  • GET /oauth2/v2.0/authorize — Authorize: shows the user picker
  • GET /:tenant/oauth2/v2.0/authorize — Authorize in tenant: shows the user picker
  • POST /oauth2/v2.0/token — Exchange token: authorization code, refresh token, and client credentials grants; client_secret_post, client_secret_basic, or private_key_jwt (a certificate assertion)
  • POST /:tenant/oauth2/v2.0/token — Exchange token in tenant: the same grants and client authentication
  • GET /oidc/userinfo — Get user info: OpenID Connect
  • GET /oauth2/v2.0/logout — End session: logout
  • GET /:tenant/oauth2/v2.0/logout — End session in tenant: logout
  • POST /oauth2/v2.0/revoke — Revoke token

Authorization code flow

  1. Redirect the user to /oauth2/v2.0/authorize with client_id, redirect_uri, scope, state, and optionally nonce, response_mode, code_challenge, and code_challenge_method
  2. The emulator renders a user picker page
  3. On selection, the emulator redirects to redirect_uri with code and state (in the query, after # for response_mode=fragment, or as an auto-submitted form for form_post)
  4. Exchange the code for tokens via POST /oauth2/v2.0/token

Issuers

Each discovery document is its own issuer, and the tokens minted through its endpoints carry it:
  • /{tenant}/v2.0/.well-known/openid-configuration (as on Entra ID) has issuer <base>/<tenant>/v2.0 and tenant-scoped endpoints (/{tenant}/oauth2/v2.0/authorize, /{tenant}/oauth2/v2.0/token, /{tenant}/discovery/v2.0/keys, /{tenant}/oauth2/v2.0/logout). ID tokens from these endpoints have iss <base>/<user's tenant>/v2.0, also through common, organizations, and consumers, whose documents name the default tenant.
  • /.well-known/openid-configuration (an emulator convenience; Entra ID has no root document) has issuer <base> and the root endpoints (/oauth2/v2.0/authorize, and so on). ID tokens from a sign-in that started at the root authorize endpoint have iss <base>.
So set your client’s issuer to either <base>/<tenant>/v2.0 (closest to production, where it is https://login.microsoftonline.com/<tenant>/v2.0) or <base>, and let it discover the endpoints. Hard-coding the root endpoints together with a tenant issuer fails the iss check.

PKCE

Include code_challenge and code_challenge_method (plain or S256) in the authorization request, and code_verifier in the token exchange.

Client credentials

Request tokens for service-to-service flows using grant_type=client_credentials with scope=https://graph.microsoft.com/.default. Returns an access_token only (no refresh_token or id_token). The token carries the client’s application_permissions as Graph roles, and any scope other than /.default is invalid_scope (AADSTS1002012). A scope for another resource, such as https://contoso.sharepoint.com/.default, gives a token for that resource whose roles come from resource_permissions for that resource URI. Without a grant there, the roles claim is left out, as Entra does. Certificate credentials (private_key_jwt) work too: register the certificate’s public PEM in certificates and send client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer with a client_assertion signed by its key, with an x5t#S256 (or x5t) header naming the certificate and iss and sub set to the client id. MSAL’s clientCertificate option does this. The token gets appidacr: "2". Unknown certificates, bad signatures, or a mismatched issuer answer 401 invalid_client with the matching AADSTS code; five minutes of clock skew are tolerated.
  • strict_tenants: unknown directory ids get AADSTS90002 from the token endpoint

Refresh tokens

Refresh requests require the client_id and client_secret of the client that received the refresh token. Successful requests rotate the refresh token. Legacy refresh records without a stored client binding remain supported.

ID tokens

The id_token is an RS256 JWT containing sub, oid, tid (tenant ID), email, name, preferred_username, ver (“2.0”), and optional nonce.

Signing key

ID tokens are signed with a key stored in emulator state (kid: baseline-microsoft-1), so they stay valid across snapshot restores and persisted restarts. Set microsoft.signing_key.private_jwk to an RSA private JWK to pin the key. See OIDC signing keys.

Microsoft Graph

Graph v1.0 and beta are served on the same port as sign-in, at /v1.0/... and /beta/.... Every Graph response carries request-id, client-request-id (echoed when sent), and @odata.context, and errors use Graph’s envelope: { "error": { "code", "message", "innerError": { "date", "request-id", "client-request-id" } } }.

What is emulated

The Graph surface is Outlook in one mailbox store, so a change made through one endpoint shows up in every related one (an invite is a calendar item and an Inbox message; declining removes the event and updates the organizer). Behavior was recorded from a real Microsoft 365 tenant and the recordings are replayed against the emulator on every test run (see Fidelity).
  • Directory: /me and /users with $select, $filter, $orderby, $search, $count, $top and $skiptoken under Graph’s directory rules (default and advanced queries with ConsistencyLevel: eventual), User.ReadBasic.All field limits, and user writes: POST /users, PATCH (self-service fields on /me), DELETE.
  • Mailboxes for every seeded user and shared mailbox, with the well-known folders (Inbox, Drafts, Sent Items, Deleted Items, Junk Email, Archive, Outbox and others), plus mailbox settings and master categories.
  • Folders: list, get, create, update, delete, child folders, move, copy, permanentDelete, delta and $count.
  • Messages:
    • list, get, $value (MIME), create (JSON or base64 MIME), update, delete, permanentDelete, move and copy;
    • delta queries per folder;
    • flags, categories, importance, inference classification and internet message headers;
    • change keys and etags that advance on every change.
  • Sending:
    • send, sendMail, reply, replyAll and forward, plus their create* draft variants;
    • delivery into recipients’ Inboxes with threading (conversationId, conversationIndex, In-Reply-To);
    • Sent Items copies, NDRs for unknown internal recipients, and read and not-read receipts;
    • Inbox rules applied at delivery, automatic replies from mailboxSettings, focused or other classification from per-sender overrides, and asynchronous post-delivery processing.
  • Attachments: file and item attachments, inline images, $value, and large file upload sessions with chunked PUTs to the upload URL.
  • Search folders (mailSearchFolder): dynamic membership from filterQuery over source folders, counts, PATCH of the definition, and the errors for writes into a search folder.
  • Calendar:
    • events CRUD on the default and secondary calendars, calendar groups, calendarView, instances and calendarView/delta;
    • Windows and IANA time zones with DST, Prefer: outlook.timezone, all-day events;
    • recurring series (all pattern and range types) expanded in the recurrence time zone, occurrence ids, exceptions and cancelled occurrences;
    • meetings: invites delivered as eventMessageRequest with a tentative copy in each internal attendee’s calendar, accept, tentativelyAccept (with proposals), decline, cancel, forward, organizer updates and response tracking;
    • getSchedule, findMeetingTimes (with room availability from locationConstraint), reminderView, dismiss and snooze, file and item attachments with upload sessions, and open extensions;
    • $expand=microsoft.graph.eventMessage/event from a meeting message to its calendar item.
  • Calendar sharing: calendarPermissions on any calendar. Sharees with Calendars.Read.Shared or Calendars.ReadWrite.Shared reach exactly the shared calendars through /users/{owner}/..., and each share or role change sends the sharee a sharing message with a copy in the owner’s Sent Items.
  • Rooms and places: /places/microsoft.graph.room, /places/microsoft.graph.roomList, rooms of a list, get and PATCH. Rooms are resource mailboxes: they appear in free/busy, accept a meeting request when free and decline it on a conflict, and findMeetingTimes with suggestLocation offers the free ones that belong to a room list. Requests more than 180 days out are declined.
  • Microsoft 365 groups: /groups, members and owners, memberOf and transitiveMemberOf, and the group calendar (/groups/{id}/events, /calendar, /calendarView) with the group as organizer.
  • Microsoft To Do: task lists, tasks, checklist items, linked resources, file attachments, delta and change notifications, with the optional Flagged Emails list synced from message flags. The retired beta /outlook/tasks, taskFolders and taskGroups read and write the same tasks.
  • Contacts: contacts and contact folders (with child folders) CRUD, computed displayName, $filter including emailAddresses/any(...), delta, photos, open extensions and permanentDelete.
  • Lists and ids: every Windows time zone from supportedTimeZones (or IANA with TimeZoneStandard=Iana), supportedLanguages, and translateExchangeIds for all five id types.
  • Inbox rules (messageRules) and inference classification overrides.
  • OData:
    • $select, $filter (comparison, logical, startswith, contains, lambda any and all on collections such as recipients and categories), $orderby with the same “inefficient filter” restrictions as Exchange;
    • $top, $skip, $count, $search, $expand=attachments;
    • server-driven paging through @odata.nextLink.
  • Protocol:
    • /v1.0 and /beta, case-insensitive segments, and users('...') key syntax;
    • $batch with dependsOn;
    • Graph error bodies and the request-id, client-request-id and x-ms-ags-diagnostic headers;
    • Prefer: IdType="ImmutableId" and outlook.body-content-type.
  • Change notifications: /subscriptions CRUD with the notification URL validation handshake (for lifecycleNotificationUrl too) and the 10070 minute expiration limit. Notifications for created, updated and deleted messages, folders, events, contacts and To Do tasks are POSTed to your endpoint in the shapes recorded from the real service.
  • Identity and permissions:
    • access tokens are RS256 JWTs with scp (delegated) or roles (app-only); scp holds every scope the user has consented to for the client, as in Entra ID;
    • every operation checks the least-privileged Graph permission;
    • app-only tokens get /me errors, and application access policies can restrict them to some mailboxes;
    • delegated access to other mailboxes follows FullAccess, SendAs and SendOnBehalf grants.

Directory queries

PATCH /me takes the self-service fields such as jobTitle, mobilePhone, businessPhones, officeLocation, and preferredLanguage. Seeded users are the same records the sign-in picker shows, so a user’s Graph id is the oid in their ID token. /users takes $select, $filter, $top (1 to 999), $orderby, $count, $search, and pages with @odata.nextLink ($skiptoken). Filters follow Graph’s directory rules per property: a default query allows eq, in, and startswith on names, mail, and identifiers, and any() on businessPhones, proxyAddresses, and otherMails; ne, not, endsWith, range operators, sorting with a filter, and properties such as department or city need an advanced query (ConsistencyLevel: eventual plus $count=true or $search). Errors otherwise are Graph’s (Request_UnsupportedQuery, Directory_ExpiredPageToken). An app with only User.ReadBasic.All sees the basic profile fields. POST /users needs accountEnabled, displayName, mailNickname, userPrincipalName on a verified domain (a seeded user’s domain or one in verified_domains), and a passwordProfile whose password meets Entra’s complexity rule.

Mail lists and ids

Each user and shared mailbox has Graph’s distinguished folders (inbox, drafts, sentitems, deleteditems, archive, junkemail, outbox, and the rest) under msgfolderroot, and every mailbox route works under /me and under /users/{id | userPrincipalName}. Lists take Exchange’s OData rules, page with @odata.nextLink, and default to 10 items. As on Exchange, moving a message gives it a new id, every change gives it a new changeKey, and DELETE removes a message rather than moving it to Deleted Items.

Paths

Microsoft Graph (/v1.0 and /beta; {user} is me, users/{id} or users/{userPrincipalName}):
  • GET, PATCH /me; GET, POST /users; GET /users/$count; GET, PATCH, DELETE /users/{id}
  • /{user}/memberOf[/microsoft.graph.group], /{user}/transitiveMemberOf[/microsoft.graph.group]
  • /{user}/mailFolders[/{id}][/childFolders|/messages|/move|/copy|/permanentDelete], /{user}/mailFolders/delta
  • /{user}/messages[/{id}], /{user}/mailFolders/{id}/messages/delta, /{user}/messages/{id}/$value
  • /{user}/messages/{id}/{send|reply|replyAll|forward|createReply|createReplyAll|createForward|move|copy|permanentDelete}
  • /{user}/sendMail
  • /{user}/messages/{id}/attachments[/{id}][/$value], /{user}/messages/{id}/attachments/createUploadSession
  • /{user}/messages/{id}/extensions[/{id}]
  • /{user}/mailFolders/inbox/messageRules[/{id}], /{user}/inferenceClassification/overrides[/{id}]
  • /{user}/mailboxSettings, /{user}/outlook/masterCategories, /{user}/outlook/supportedTimeZones, /{user}/outlook/supportedLanguages
  • /{user}/translateExchangeIds
  • /{user}/events[/{id}], /{user}/events/{id}/{accept|tentativelyAccept|decline|cancel|forward|dismissReminder|snoozeReminder|permanentDelete|instances|attachments|extensions}
  • /{user}/calendar[/events|/calendarView|/getSchedule], /{user}/calendars[/{id}[/events|/calendarView]], /{user}/calendarGroups[/{id}[/calendars]]
  • /{user}/calendarView[/delta], /{user}/findMeetingTimes, /{user}/reminderView(startDateTime='...',endDateTime='...')
  • /{user}/contacts[/{id}[/photo|/extensions|/permanentDelete]], /{user}/contacts/delta
  • /{user}/contactFolders[/{id}[/childFolders|/contacts]], /{user}/contactFolders/delta
  • /{user}/calendar[s/{id}]/calendarPermissions[/{id}]
  • /places/microsoft.graph.room, /places/microsoft.graph.roomList, /places/{list}/microsoft.graph.roomList/rooms, /places/{id|email}
  • /groups[/{id}[/members|/owners]] (GET, POST, PATCH, DELETE), /directory/deletedItems/{id}, /groups/{id}/events[/{id}], /groups/{id}/calendar[/events|/calendarView], /groups/{id}/calendarView
  • /{user}/todo/lists[/{id}[/tasks[/{id}[/checklistItems|/linkedResources|/attachments]]]], /{user}/todo/lists/delta, .../tasks/delta
  • /beta/{user}/outlook/tasks[/{id}[/complete]], /beta/{user}/outlook/taskFolders[/{id}/tasks], /beta/{user}/outlook/taskGroups
  • /subscriptions[/{id}]
  • POST /$batch
The generated coverage matrix in docs/coverage.md lists every operation with its permissions and evidence.

Delivering mail in tests

Mail is delivered graph.delivery_delay_ms after a send (default 1000), as Exchange delivers asynchronously. Seed graph: { delivery_delay_ms: 0 } to deliver before the send call returns, or call flushTransport(store) from @deploybaseline/microsoft to deliver everything pending. Programmatic use can deliver pending mail without waiting for the delay:

Fidelity

Behavior is specified as rules in conformance/spec/catalog.ts. Each rule is marked verified (a committed recording from a real Microsoft 365 test tenant backs it), observed (real service traffic recorded by others, see docs/research/), documented (from Microsoft documentation only) or assumed. The conformance suite replays every recorded scenario against the emulator and compares normalized responses step by step; it runs with the package tests. See conformance/README.md for the methodology, how to record against your own tenant, and docs/coverage.md for current results.

SharePoint and OneDrive

A stateful SharePoint Online served through Microsoft Graph (/v1.0, /beta) and the SharePoint REST API (/{site}/_api) from one state. A file written through Graph is readable through REST at once with the same ids, versions and permissions, and the reverse. SharePoint routes answer only after a site exists, from the seed config or the control plane; until then they answer like any path Graph does not know.

Seed config for SharePoint

  • application_permissions: Graph app roles
  • certificates: app-only REST needs a certificate credential, as in Entra
  • https://contoso.sharepoint.com under resource_permissions: SharePoint app roles
  • owners: Full Control; the first owner is the site collection admin
  • members: Edit
  • grants: Sites.Selected grants for an app
Sites take group (a group from groups: makes a group-connected team site), visitors, subsites, custom_script and grants ({app_id, roles, list?, item?}). Lists take template (genericList, documentLibrary or a template number), url, description, folders, files (path, content or content_base64, fields, author), items, versioning, permissions (break inheritance: {email: read|contribute|edit|design|full}) and columns of type text, note, number, currency, choice, multichoice, dateTime, dateOnly, boolean, person, personMulti, lookup or hyperlink (or a Graph columnDefinition under graph). The other sharepoint options are in the seed config table.

Tokens

  • Graph: scope https://graph.microsoft.com/.default (app) or Graph scopes (delegated), with Sites.* and Files.* permissions.
  • REST: scope https://contoso.sharepoint.com/.default. The token audience is the SharePoint app id 00000003-0000-0ff1-ce00-000000000000, as Entra issues it, and its roles or scopes are SharePoint’s (Sites.FullControl.All, AllSites.Manage and the rest). App-only REST refuses client secret tokens (401 “Unsupported app only token.”) and accepts certificate assertions (see Client credentials).

Graph

  • Sites: /sites (search), /sites/root, /sites/{host}:/{path}, composite ids, /sites/getAllSites(), subsites, site columns, content types, and /sites/{id}/permissions (Sites.Selected grants).
  • Lists: lists, columns, content types (addCopy), list permissions, and list items with typed fields, delta, versions (restoreVersion), permissions and createLink.
  • Drives: /drives/{id}, a site’s /drive and /drives, a list’s /drive, /groups/{id}/drive, and drive search.
  • Drive items: by id or path (root:/a/b.txt:), children, content (simple upload and download), createUploadSession, copy with a monitor URL, move and rename through PATCH, versions, checkout, checkin, discardCheckout, publish, delta, search, createLink, invite and permissions.
  • OneDrive: /me/drive, /me/drives, /users/{id}/drive and /users/{id}/drives address a business drive named OneDrive, the Documents library of https://contoso-my.sharepoint.com/personal/{upn with _}, created on first use. Other delegated users get 403 accessDenied.
  • Shares: /shares/{id} for u! encoded sharing links and item URLs, with driveItem, root, listItem, list, site and permission.
  • Search and notifications: /search/query for sites, lists, list items, drives and drive items, and /subscriptions on drive roots and lists (updated only, as Graph documents), notified from the SharePoint change log. $batch carries SharePoint answers with their SPLogId and Cache-Control headers.

REST

Supported: contextinfo, web, site, lists, items, fields (addfield, CreateFieldAsXml), content types, folders, files (upload, $value, chunked StartUpload, ContinueUpload, FinishUpload and CancelUpload, SP.MoveCopyUtil), versions, check out and in, publish and content approval, attachments, role assignments and inheritance, effective permissions, site groups and users, recycle bin (both stages), GetChanges, GetListItemChangesSinceToken, RenderListDataAsStream, CAML GetItems, search, list webhooks and multipart $batch. Answers are JSON (odata=verbose, minimalmetadata or nometadata) or Atom when the Accept header names no JSON flavor. PnPjs works with spfi("<emulator origin>/sites/projects"), and the package tests run PnPjs and the Kiota Graph SDK (drives and sites) against the emulator. Graph hands out download, upload session and copy monitor URLs on the SharePoint host, signed with tempauth and taking no Authorization header:
  • GET /sites/:site/_layouts/15/download.aspx — Download a file (also HEAD)
  • PUT /_api/v2.0/drives/:driveId/items/:itemId/uploadSession — Upload a byte range to a session (GET reads its status, an empty POST commits a deferred session, DELETE cancels it)
  • GET /_api/v2.0/monitor/:monitorId — Read a folder copy’s status
  • GET /sites/:site/_api/v2.0/drives/:driveId/items/:itemId — Read a file copy’s status (force303=1, the new item’s v2.0 URL)

Control plane

  • GET /_baseline/sharepoint — Summary of sites, lists, jobs and sessions
  • POST /_baseline/sharepoint/sites — Provision a site, in the format of a sites entry
  • POST /_baseline/sharepoint/clock — {advance_ms}, {freeze: true} or {real: true}; drives search lag, upload session expiry and copy jobs
  • POST /_baseline/sharepoint/jobs/run — Finish pending copy jobs
  • POST /_baseline/sharepoint/changes/trim — {keep_last: n}; older delta and change tokens then need a resync
  • GET /_baseline/sharepoint/faults — List fault rules
  • POST /_baseline/sharepoint/faults — Add a fault rule: {api: graph|rest, method, path (regex), status, count, retry_after, code, message}
  • DELETE /_baseline/sharepoint/faults — Remove every fault rule, or one with ?id=
  • GET /_baseline/sharepoint/snapshot — SharePoint state as JSON
  • POST /_baseline/sharepoint/restore — Replace SharePoint state with a snapshot

Behavior worth knowing

Graph permission ids are the base64 of the principal’s login name; SharePoint groups carry a sharePointGroup facet and site collection administrators list as owners. invite adds a role assignment the invited user can use at once. File and folder names follow the service’s rules on each surface: Graph and REST refuse the same names with their own messages, REST methods that take a URL (Files/add(url=), folders/add) trim trailing dots and spaces while AddUsingPath refuses them, and a REST request path longer than 401 characters or holding *, <, > or ? answers 401 as the service’s web server does. Recycling, deleting or moving a list’s root folder acts on the list itself. With allow_subsite_creation: false, web/webs/add answers 400 InvalidOperationException. Thirty-five SharePoint scenarios are recorded against a real tenant and replayed against the emulator. The operation inventory, generated coverage report, behavioral catalog with evidence, decisions and gap analysis are in docs/sharepoint.

Seed config

A sign-in only config:
The Microsoft Graph side (Outlook mail, calendar, contacts, To Do, places, groups, and the directory) takes more keys: Graph profile fields on users, shared_mailboxes, mailbox_permissions, verified_domains, messages, rooms, room_lists, groups, and graph options. A fuller example:
  • application_permissions: Optional. Roles in client_credentials tokens; omit for every modeled permission.
  • delegated_permissions: Optional. Scopes granted for “.default” delegated requests; omit for every modeled permission.
  • visibility: Private groups are closed to non-members
  • delivery_delay_ms: send to delivery delay; 0 delivers synchronously
  • strict_auth: true rejects unknown bearer tokens with 401
  • application_access_policies: restrict app-only clients to some mailboxes

Inspector

  • GET / — Inspector: the directory, mailboxes, subscriptions, and OAuth apps
Open the emulator’s root URL in a browser for the directory (users, shared and room mailboxes, groups), each mailbox’s mail, calendar, contacts, and To Do, change notification subscriptions, and OAuth apps.

Limits

  • Timing of asynchronous Exchange processing is approximate.
  • Directory query rules for /users (which properties and operators need an advanced query) follow Microsoft’s documentation and are not recorded live.
  • Throttling, delta token expiry and application access policies are documented only and marked so in the coverage matrix.
  • Not modeled: private event masking for sharees, delegate meeting forwarding, group conversations, room list based location search beyond the seeded rooms, and To Do list sharing.
  • $search matches KQL-style terms and properties directly. There is no search index, so no ranking and no indexing delay.
  • SharePoint: sites are created by seed config or the control plane, never through Graph (POST /sites is not a Graph v1.0 operation). Not modeled: the client object model (_vti_bin/client.svc), the SOAP services, the term store, hub sites, site pages and navigation, tenant administration and SP.AppContextSite. Those operations answer 501.

Conformance

See CONFORMANCE.md for every provider.