Last updated
We want people to ask questions about our SharePoint data in plain English, but no chatbot tool talks to SharePoint's list API sensibly.
Before each change advisory board, the change manager puts the pack together. For every open change request it shows the asset the change touches and any approval still outstanding. The data sits in three SharePoint lists, Change Requests, Assets and Approvals, linked by lookups, so building the pack by hand means filtering one list and then opening the other two in separate tabs for each request.
They would rather type the question: which open changes touch production assets, and which of those are still waiting on an approval? A chat tool that writes SQL could answer that, if it had a database to query. Pointed at SharePoint, no text-to-SQL tool does well, because the list API serves pages to a browser and a lookup column comes back as an ID that a model cannot turn into an asset name on its own.
What the model needs to see
A text-to-SQL model writes better SQL against a schema that reads like the business. So SPSync replicates the lists into a SQL database, refreshes it on a schedule, and the chat tool points at that database. It never needs SharePoint access of its own.
| Title | Client lookup | Tags multi-choice | Owner person | Due |
|---|---|---|---|---|
| Riverside | Acme Ltd | Design; Build | Ada Lovelace | 2026-11-30 |
| Harbour | Borealis plc | Build | Alan Turing | 2027-02-14 |
then sync
- id
- title
- client_id → clients.id
- owner_id → sp_users.id
- due
- is_deleted soft delete
- sp_modified
- id
- title
- …
- project_id
- tag
One row per value: Riverside has two.
- id
- display_name
One users table shared by every list.
In the copy, a lookup is a key column, so a generated join reads “task assigned to person”
where the API would say “task with lookup ID 47”. A multi-choice column is a child table the
model can join on. Deleted items keep their row with sp_deleted_at set, so “how many tasks
were removed last month” has an answer. For tools that do better against one wide table,
SPSync can generate reporting views that pre-join person names.
From the question to the board pack
Choose the site and the three lists, and replicate them to whichever database the chat tool supports, such as Postgres, SQL Server or Azure SQL. Give the tool a read-only role with access to those tables or views. A schedule of a few minutes keeps answers close to SharePoint; hourly is enough for a weekly board.
When the change manager asks the question, the tool works through it as reads against the replica:
- Read the schema, which shows
change_requests,assetsandapprovals, with the asset lookup on each change request held asasset_id. - Join open change requests to
assetson that key and keep the production ones. - Join
approvalson the change request and keep the rows where an approval is still pending. - Return a table with the item ID of each change request, so the reader can open the original in SharePoint.
That table is the packet. The change manager reviews it, along with the SQL behind it if the tool shows its working, and takes it to the board, where people decide which changes go ahead. Approving a change or closing a request happens in SharePoint, by a person. The chat tool has no write path there, because SPSync never writes to SharePoint, and its read-only role means it cannot alter the replica either.
All of this runs on relational destinations, with no search or vector store. If people later ask looser questions, such as “which tasks sound like the delay on the Leeds project”, pgvector is a shipped destination that adds one embedding column per table, built from the text columns you choose. See SharePoint to pgvector.
Two worked examples
Text-to-SQL on SharePoint Data: Why the Schema Decides follows one project office list and one question through a raw export and a replica, with the SQL a model writes against each and how to test its answers.
Local AI over SharePoint Data with SQLite or DuckDB replicates a legal team’s Matters list into a DuckDB file on a solicitor’s laptop, queried by a local Ollama model through a read-only MCP server, with nothing about the data leaving the machine.
If the chat tool is one of several agents reading the same lists, an agent data layer for SharePoint shows how they share one replica, each with its own database login and row-level rules.
Desktop, Azure or command line
All three editions run the same engine, write the same tables and use the same licence key, so the choice comes down to who looks after the sync.
Desktop app
For macOS and Windows. Sign in with Microsoft, tick the lists, choose a database and sync from your own machine on the schedule you set. Delegated sign-in needs no admin unless your tenant requires admin consent for apps.
In your Azure
SPSync in your Azure, a managed application for your own subscription, is deployed with you on a call while the Marketplace listing is prepared. Once deployed it is always on, so the sync keeps running with every laptop closed.
Command line
Every replication is a YAML file you can review and commit. Run it from a server, a container or a CI pipeline, and re-run it safely any time.
Set it up
Ready to run this against your own site? The setup guide takes you from install to a first replication, and the SharePoint connection guide covers signing in.
Script it
Everything you can click, you can script. The CLI, YAML config and automation below reproduce this scenario exactly. See the CLI reference.
spsync auth login --client-id 00000000-0000-0000-0000-000000000000
spsync export-config --output replication.yaml --pipeline-name chat_with_your_sharepoint_data --prefix chat_with_your_sharepoint_data --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name chat_with_your_sharepoint_data --client-id 00000000-0000-0000-0000-000000000000 --site-url https://contoso.sharepoint.com/sites/example --all-lists
spsync doctor replication.yaml
spsync run replication.yaml pipeline_name: chat_with_your_sharepoint_data
prefix: chat_with_your_sharepoint_data
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: postgres
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: chat_with_your_sharepoint_data
scopes:
- site_url: https://contoso.sharepoint.com/sites/example
mode: entire_site
reconcile_interval_days: 7
create_reporting_views: false
Cron (PostgreSQL nightly):
# every night at 02:00
0 2 * * * cd /opt/spsync && SPSYNC_DB_CONNECTION="$(cat /etc/spsync/db.env)" uv run spsync run replication.yaml >> /var/log/spsync/run.log 2>&1 GitHub Actions:
# .github/workflows/spsync-run.yml
name: SPSync
on:
schedule:
- cron: "0 2 * * *"
workflow_dispatch: {}
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv sync
- run: uv run spsync run replication.yaml
env:
SPSYNC_DB_CONNECTION: ${{ secrets.SPSYNC_DB_CONNECTION }}
SPSYNC_LICENCE_KEY: ${{ secrets.SPSYNC_LICENCE_KEY }} Azure DevOps:
# azure-pipelines.yml
schedules:
- cron: "0 2 * * *"
branches: { include: [main] }
always: true
steps:
- script: uv sync
- script: uv run spsync run replication.yaml
env:
SPSYNC_DB_CONNECTION: $(SPSYNC_DB_CONNECTION)
SPSYNC_LICENCE_KEY: $(SPSYNC_LICENCE_KEY) Windows (PowerShell + Task Scheduler):
# Windows Task Scheduler action (see /docs/cli/automation/)
$env:SPSYNC_DB_CONNECTION = (Get-Content C:\spsync\db.env)
uv run spsync run C:\spsync\replication.yaml
schtasks /Create /SC DAILY /ST 02:00 /TN "SPSync" /TR "powershell -File C:\spsync\run.ps1" macOS (launchd):
# ~/Library/LaunchAgents/io.spsync.run.plist, then: launchctl load ~/Library/LaunchAgents/io.spsync.run.plist Secrets (licence key, connection strings) come from the environment, so never write them into the YAML. See scheduling and secrets.