Connecting SharePoint
Moʻolelo indexes drive items — files and folders in SharePoint document libraries — for the SharePoint sites you list. It uses Microsoft Graph delta queries for backfill and Microsoft Graph subscriptions for steady-state notifications.
Prerequisites
- A Microsoft 365 tenant with SharePoint Online.
- An Azure AD admin who can register an app and grant application permissions (admin consent required).
- Network reachability: Moʻolelo's webhook endpoint must be reachable from Microsoft Graph's notification IPs. The endpoint must respond to a one-time validation request within 10 seconds.
Step 1 — register an Azure AD application
In the Azure Portal under "Microsoft Entra ID → App registrations":
- Create a new registration.
- Under Certificates & secrets, generate a client secret. Save the secret value immediately — Azure shows it only once.
- Under API permissions, add Microsoft Graph application permissions:
Sites.Read.AllFiles.Read.All
- Have a tenant admin click Grant admin consent. Without admin consent, the app cannot read SharePoint data.
Make a note of:
- Tenant ID (the Azure AD tenant GUID).
- Application (client) ID.
- Client secret value.
Step 2 — register the connector
Insert a row into connector_configs:
INSERT INTO connector_configs
(tenant_id, source, instance, mode, resources, raw_config)
VALUES (
'default',
'sharepoint',
'contoso', -- your "instance" label
'backfill',
'["contoso.sharepoint.com:/sites/intranet",
"contoso.sharepoint.com:/sites/engineering"]'::jsonb, -- site identifiers
'{
"azure_tenant_id": "11111111-2222-3333-4444-555555555555",
"client_id": "your-app-client-id",
"client_secret": "your-app-client-secret",
"scope": "https://graph.microsoft.com/.default"
}'::jsonb
);The resources are SharePoint site identifiers in Microsoft Graph's <host>:<server-relative-path> format. Find them by navigating to a SharePoint site in your browser and copying the URL path; for the root site, use <host>:/ (with a trailing slash).
Moʻolelo authenticates with Azure AD using the client-credentials flow and caches access tokens. Refresh is automatic.
Step 3 — create webhook subscriptions
Microsoft Graph webhooks are called subscriptions and are bound to specific resources. You create one per drive you want notifications from — typically the default document library of each site.
A subscription request to Graph looks like:
curl -X POST \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
https://graph.microsoft.com/v1.0/subscriptions \
-d '{
"changeType": "updated",
"notificationUrl": "https://<your-moolelo-host>/webhooks/sharepoint/contoso",
"resource": "sites/<site-id>/drives/<drive-id>/root",
"expirationDateTime": "2026-05-09T19:00:00Z",
"clientState": "<random-secret>"
}'Microsoft Graph enforces a maximum subscription lifetime of around 60 minutes for drive items. Subscriptions must be renewed before expiry; an operator-side scheduler that calls PATCH /subscriptions/<id> periodically is the standard pattern.
The clientState value goes into Moʻolelo's deployment as WEBHOOK_INGRESS_SHAREPOINT_SECRET. Every notification carries the clientState field; the webhook receiver constant-time-compares it to the configured secret.
The validation handshake
The first time you create a subscription, Microsoft Graph POSTs the notificationUrl with a ?validationToken=<random> query parameter and an empty body. Moʻolelo's webhook receiver echoes the token in text/plain within the 10-second window, which Graph requires before activating the subscription. You don't have to do anything — the handshake is automatic — but if your network path adds enough latency to push the round-trip past 10 seconds, subscriptions won't activate.
Step 4 — flip to webhook mode
After backfill completes:
UPDATE connector_configs
SET mode = 'webhook'
WHERE tenant_id = 'default' AND source = 'sharepoint';What gets indexed
Per drive item, Moʻolelo indexes:
- File name and path — surfaced as both lexical text and structural metadata.
- File contents — Microsoft Office formats (Word, Excel, PowerPoint), PDFs, plain text, and Markdown are extracted by Docling and chunked along the document's heading hierarchy.
- Author and last modifier — for residency routing.
- Source URL — direct link back to the file in SharePoint.
What is not indexed today:
- SharePoint list items (only drive items / files are covered).
- Permission inheritance from sub-folders. Drive-item permissions are flat in our model — sub-folder permissions are evaluated at query time via the per-source policy hook.
- OneDrive personal libraries. The connector only watches SharePoint sites listed in
resources.
Permissions visible in search results
SharePoint's permission model is rich (sharing links, group memberships, broken inheritance, conditional access). Moʻolelo's strategy:
- At ingestion, extract the drive item's effective permission set from Graph's
/drives/{id}/items/{id}/permissionsendpoint. - Project the permissions into authority records keyed by SharePoint principal IDs.
- At query time, match the calling user's Microsoft account against the authority records.
When a permission can't be resolved offline (typically: anonymous sharing links, or conditional-access-gated content), Moʻolelo falls back to a live Graph permission check. This is slower (an extra round-trip per document) but preserves correctness.
Troubleshooting
- Subscription creation 400. The
notificationUrlhas to be HTTPS and externally reachable. Microsoft Graph validates this synchronously. Self-signed certificates are not accepted. Access deniederrors during backfill. Confirm admin consent was granted on the Azure AD app permissions (Step 1). Without consent, the app sees no SharePoint sites at all.- Webhook handshake timing out. Reduce the network hop count between Microsoft Graph's outbound IPs and your webhook receiver. CDN intermediaries that buffer the request body break the handshake.
- Drive items appear in search but their content is empty. Docling couldn't parse the file format. Check the parser logs for
parse failed; Moʻolelo falls back to indexing just the file name in this case.

