Share an Excel File in OneDrive with a Link Using Graph
Creating a report in OneDrive or SharePoint is half the job; the other half is getting it to the people who need it, with the right level of access and nothing more. Clicking "Share" by hand every month does not scale, and an attachment loses the advantage of a single live file. Microsoft Graph exposes the same sharing model as the OneDrive and SharePoint interfaces: links with a type (view or edit) and a scope (anyone, people in your organisation, or specific people), optional expiry, and direct invitations to named recipients. This page creates each kind from Python, invites recipients without sending Microsoft's notification email so your own report email can carry the link, lists the permissions on a file and removes links that should not outlive the reporting period. It belongs to Automating Excel Online with Microsoft Graph.
Prerequisites
pip install requests
You need a Microsoft Graph token with Files.ReadWrite (delegated) or Files.ReadWrite.All (application), the drive ID and the item ID of the workbook — for example from the upload in Create a New Workbook in OneDrive with Microsoft Graph. What a link may do is limited by your tenant's sharing policy: if administrators have disabled anonymous links, asking for one fails, whatever the code says. The outputs below come from a mocked Graph API (the responses library) that returns the documented response shapes, so they can be shown without a tenant.
Step 1 — Choose the link type and scope
A sharing link has two independent settings, and the combination decides who can do what:
For internal reports, a view link with organization scope is the usual choice: anyone signed in to your Microsoft 365 tenant can open it, nobody outside can, and nobody can change the numbers. anonymous links work for anyone who holds the URL, which makes them easy to forward and impossible to control — use them only for genuinely public data, and always with an expiry. users links work only for people who have been granted access, which suits sensitive reports such as payroll or board packs.
Think about what the recipients will actually do with the file. Most report readers only look: they filter, sort and maybe copy a figure into an email, none of which needs edit rights — Excel for the web lets viewers sort and filter a view of their own without changing the file. Edit access matters for workbooks people are expected to fill in, such as a forecast return or a checklist, and those are better served by inviting named people than by a link anyone in the company can open. Starting from the narrowest access that works and widening it on request is easier than discovering later that a salary file was reachable by the whole tenant.
Step 2 — Create a link
A link is created with a POST to the item's createLink action. The response is a permission whose link.webUrl is the URL to send:
import os
from datetime import datetime, timedelta, timezone
import requests
GRAPH = "https://graph.microsoft.com/v1.0"
DRIVE, ITEM = "b!reports-drive", "01ITEMSALES0926"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['GRAPH_TOKEN']}"
def create_link(kind="view", scope="organization", expires_in_days=None):
body = {"type": kind, "scope": scope}
if expires_in_days:
expiry = datetime.now(timezone.utc) + timedelta(days=expires_in_days)
body["expirationDateTime"] = expiry.strftime("%Y-%m-%dT%H:%M:%SZ")
r = session.post(f"{GRAPH}/drives/{DRIVE}/items/{ITEM}/createLink", json=body, timeout=60)
r.raise_for_status()
return r.json()
org_view = create_link()
print(org_view["link"]["scope"], org_view["link"]["type"], org_view["roles"], org_view["link"]["webUrl"])
public = create_link("view", "anonymous", expires_in_days=14)
print(public["link"]["scope"], "expires", public["expirationDateTime"][:10])
createLink is idempotent for the same type and scope: asking for an organisation view link twice returns the existing link rather than making a second one, so a monthly job can call it every time without accumulating links. If each month's report is a separate file, each gets its own link; if the job overwrites one file in place, the link people bookmarked keeps working and always shows the latest figures. expirationDateTime is a UTC timestamp in ISO 8601 format; after it the link stops working. Some settings depend on the account type — password-protected links, for instance, are available only on OneDrive for personal accounts — and your tenant can enforce a maximum expiry for anonymous links regardless of what you ask for.
Step 3 — Invite named people
To give specific people access — and optionally have Microsoft send them a notification — use invite:
def invite(emails, role="read", notify=False, message=None):
body = {"recipients": [{"email": e} for e in emails], "roles": [role],
"requireSignIn": True, "sendInvitation": notify}
if notify and message:
body["message"] = message
r = session.post(f"{GRAPH}/drives/{DRIVE}/items/{ITEM}/invite", json=body, timeout=60)
r.raise_for_status()
return r.json()["value"]
granted = invite(["maria.lopez@contoso.com", "tom.becker@contoso.com"], role="read")
for p in granted:
print(p["roles"], p["grantedToV2"]["user"]["displayName"], p["grantedToV2"]["user"]["email"])
requireSignIn: True means the recipients must sign in as themselves to open the file — the link cannot be forwarded to someone else. With sendInvitation: False, Graph grants access silently, which is useful when your own report email already contains the link and a second notification from Microsoft would be noise; set it to True with a message when the sharing email is the notification. Roles are read or write. Recipients outside your organisation are invited as guests if the tenant allows external sharing; their first visit asks them to verify their address with a one-time code, so warn them in your own email that this step is coming. Sending the link in your own email is covered in Send an Excel Report with the Microsoft Graph API, and the trade-offs of links versus attachments in Email a Download Link Instead of an Excel Attachment.
Step 4 — List and remove permissions
Every link and grant appears as a permission on the item. Listing them shows who has access; deleting one revokes it:
def permissions():
return session.get(f"{GRAPH}/drives/{DRIVE}/items/{ITEM}/permissions", timeout=60).json()["value"]
def describe(p):
if "link" in p:
return f"link {p['link']['type']}/{p['link']['scope']}" + (f" until {p['expirationDateTime'][:10]}" if "expirationDateTime" in p else "")
return f"{p['roles'][0]} for {p['grantedToV2']['user']['displayName']}"
for p in permissions():
print(f"{p['id']:<7} {describe(p)}")
for p in permissions():
if p.get("link", {}).get("scope") == "anonymous":
session.delete(f"{GRAPH}/drives/{DRIVE}/items/{ITEM}/permissions/{p['id']}", timeout=60).raise_for_status()
print("after removing anonymous links:", [describe(p) for p in permissions()])
The owner permission cannot be removed and inherited permissions from the parent folder or site appear too — leave those alone and delete only the links and grants your job created. A clean-up step like this, run when a reporting period closes, keeps old files from staying reachable through forgotten links. Recording each permission ID as you create it, in a log or a small state file, makes it easy to revoke exactly what the job granted and nothing else; Log Python Excel Script Output to a File covers the logging side.
The same listing doubles as an access audit. A short script that walks a reports folder, lists the permissions on every workbook and flags anonymous links, edit links, links without an expiry and grants to addresses outside your domain gives a security reviewer exactly what they ask for, and it takes a few minutes to run rather than an afternoon of clicking through the "Manage access" panel file by file. Run it monthly and keep its output with the period's reports.
Common pitfalls
- Anonymous edit links. Anyone holding the URL can change the numbers.
- No expiry on public links. Set
expirationDateTime; policy may cap it anyway. - Double notifications. Use
sendInvitation: Falsewhen your own email has the link. - Deleting inherited permissions. Remove only what your job created.
- Assuming the tenant allows it. Sharing policy overrides the request.
Conclusion
To share a workbook in OneDrive or SharePoint from Python, POST to the item's createLink with a type (view or edit) and scope (anonymous, organization or users), optionally with an expiry, and send the returned link.webUrl. Use invite with requireSignIn to grant named people access, silently or with a notification. List the item's permissions to audit access and delete the links and grants that should not outlive the reporting period.
Frequently asked questions
How do I create a sharing link for a OneDrive file in Python? POST to the createLink action on the drive item with a type of view or edit and a scope of anonymous, organization or users; the response's link.webUrl is the URL to share.
How do I make a sharing link expire? Include expirationDateTime as a UTC ISO 8601 timestamp in the createLink request; the link stops working after that time.
How do I share a file with specific people without sending an email? Use the invite action with their addresses, a role, requireSignIn set to true and sendInvitation set to false.
How do I remove a sharing link with Microsoft Graph? List the item's permissions, find the one for that link and send a DELETE request for that permission ID.
Related
- Up one level: Automating Excel Online with Microsoft Graph — Excel in the cloud from Python.
- Copy a Template Workbook in SharePoint with Microsoft Graph — creating per-project workbooks.
- Upload an Excel Report to SharePoint with Python — getting the report there.
- Authenticate to Microsoft Graph with MSAL for Excel Automation — the token these calls need.
More on excel online & microsoft graph
- How-toSend an Excel Report with the Microsoft Graph API
- How-toAdd a Chart to an Excel Online Workbook with Microsoft Graph
- How-toAdd, Rename and Delete Worksheets with Microsoft Graph
- How-toAppend Rows to an Excel Online Table with Microsoft Graph
- How-toAuthenticate to Microsoft Graph with MSAL for Excel Automation
- How-toCall Excel Worksheet Functions Through Microsoft Graph