[{"data":1,"prerenderedAt":2542},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive":3,"surround:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive":2510,"ancestors:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive":2519,"children:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive":2524,"related:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive":2525},{"id":4,"title":5,"body":6,"dateModified":2476,"datePublished":2476,"description":2477,"extension":2478,"faq":2479,"keyword":2488,"kind":2489,"meta":2490,"navigation":160,"path":2499,"seo":2500,"slug":2503,"stem":2504,"topics":2505,"type":2508,"__hash__":2509},"docs\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive\u002Findex.md","Run a Report When an Excel File Changes in OneDrive",{"type":7,"value":8,"toc":2464},"minimark",[9,32,37,72,96,100,122,398,413,681,684,822,826,833,1418,1433,1576,1580,1587,1928,1962,1966,1972,2157,2168,2306,2310,2336,2339,2343,2380,2384,2399,2403,2409,2415,2421,2427,2431,2460],[10,11,12,13,17,18,21,22,25,26,31],"p",{},"A scheduled job that rebuilds a report every hour does a lot of work for nothing, and is still up to an hour late when the input changes. When the input is a workbook in OneDrive or SharePoint — a sales forecast someone updates, a price list that finance maintains — it is better to react to the change itself. Microsoft Graph offers three ways to notice it, from simplest to most immediate: poll the file's ",[14,15,16],"em",{},"content tag",", ask the ",[14,19,20],{},"delta"," endpoint what changed in a folder since last time, or subscribe to ",[14,23,24],{},"webhook"," notifications. All three need the same extra care, because Excel autosaves: one editing session can produce a dozen saves, and the report should run once, after the editing stops. This page covers each approach and the debounce. It belongs to ",[27,28,30],"a",{"href":29},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002F","Automating Excel Online with Microsoft Graph",".",[33,34,36],"h2",{"id":35},"prerequisites","Prerequisites",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install requests msal flask responses\n","bash","",[45,46,47],"code",{"__ignoreMap":43},[48,49,52,56,60,63,66,69],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sMTad","pip",[48,57,59],{"class":58},"srMev"," install",[48,61,62],{"class":58}," requests",[48,64,65],{"class":58}," msal",[48,67,68],{"class":58}," flask",[48,70,71],{"class":58}," responses\n",[10,73,74,75,78,79,82,83,86,87,91,92,95],{},"A Graph access token with ",[45,76,77],{},"Files.Read"," (delegated) or ",[45,80,81],{},"Files.Read.All"," \u002F ",[45,84,85],{},"Sites.Read.All"," (application) is needed; see ",[27,88,90],{"href":89},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation\u002F","Authenticate to Microsoft Graph with MSAL for Excel Automation",". Live calls are replaced by the ",[45,93,94],{},"responses"," mocking library in the examples, and the webhook receiver is exercised with Flask's test client, so everything runs offline.",[33,97,99],{"id":98},"step-1-poll-the-files-ctag","Step 1 — Poll the file's cTag",[10,101,102,103,106,107,110,111,114,115,118,119,121],{},"The lightest approach is a scheduled poll that only does real work when the file has changed. Every drive item has two version tags: ",[45,104,105],{},"eTag"," changes on ",[14,108,109],{},"any"," change, including a rename or a sharing update, while ",[45,112,113],{},"cTag"," changes only when the ",[14,116,117],{},"content"," changes. For triggering a report, ",[45,120,113],{}," is the one to watch:",[38,123,127],{"className":124,"code":125,"language":126,"meta":43,"style":43},"language-python shiki shiki-themes github-light github-dark-high-contrast","import json\nimport pathlib\nimport requests\n\nGRAPH = \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\nSTATE = pathlib.Path(\"last_ctag.json\")\n\ndef content_changed(token, item_path):\n    url = f\"{GRAPH}\u002Fme\u002Fdrive\u002Froot:\u002F{item_path}?$select=id,name,cTag,lastModifiedDateTime\"\n    item = requests.get(url, headers={\"Authorization\": f\"Bearer {token}\"}, timeout=30).json()\n    previous = json.loads(STATE.read_text()) if STATE.exists() else {}\n    changed = previous.get(item[\"id\"]) != item[\"cTag\"]\n    previous[item[\"id\"]] = item[\"cTag\"]\n    STATE.write_text(json.dumps(previous))\n    return changed, item\n","python",[45,128,129,139,147,155,162,175,192,197,210,247,301,332,361,380,389],{"__ignoreMap":43},[48,130,131,135],{"class":50,"line":51},[48,132,134],{"class":133},"s-kum","import",[48,136,138],{"class":137},"skGVy"," json\n",[48,140,142,144],{"class":50,"line":141},2,[48,143,134],{"class":133},[48,145,146],{"class":137}," pathlib\n",[48,148,150,152],{"class":50,"line":149},3,[48,151,134],{"class":133},[48,153,154],{"class":137}," requests\n",[48,156,158],{"class":50,"line":157},4,[48,159,161],{"emptyLinePlaceholder":160},true,"\n",[48,163,165,169,172],{"class":50,"line":164},5,[48,166,168],{"class":167},"sP0c6","GRAPH",[48,170,171],{"class":133}," =",[48,173,174],{"class":58}," \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\n",[48,176,178,181,183,186,189],{"class":50,"line":177},6,[48,179,180],{"class":167},"STATE",[48,182,171],{"class":133},[48,184,185],{"class":137}," pathlib.Path(",[48,187,188],{"class":58},"\"last_ctag.json\"",[48,190,191],{"class":137},")\n",[48,193,195],{"class":50,"line":194},7,[48,196,161],{"emptyLinePlaceholder":160},[48,198,200,203,207],{"class":50,"line":199},8,[48,201,202],{"class":133},"def",[48,204,206],{"class":205},"s_Opv"," content_changed",[48,208,209],{"class":137},"(token, item_path):\n",[48,211,213,216,219,222,225,229,231,234,237,239,242,244],{"class":50,"line":212},9,[48,214,215],{"class":137},"    url ",[48,217,218],{"class":133},"=",[48,220,221],{"class":133}," f",[48,223,224],{"class":58},"\"",[48,226,228],{"class":227},"sSjpA","{",[48,230,168],{"class":167},[48,232,233],{"class":227},"}",[48,235,236],{"class":58},"\u002Fme\u002Fdrive\u002Froot:\u002F",[48,238,228],{"class":227},[48,240,241],{"class":137},"item_path",[48,243,233],{"class":227},[48,245,246],{"class":58},"?$select=id,name,cTag,lastModifiedDateTime\"\n",[48,248,250,253,255,258,262,264,266,269,272,275,278,280,283,285,287,290,293,295,298],{"class":50,"line":249},10,[48,251,252],{"class":137},"    item ",[48,254,218],{"class":133},[48,256,257],{"class":137}," requests.get(url, ",[48,259,261],{"class":260},"sa561","headers",[48,263,218],{"class":133},[48,265,228],{"class":137},[48,267,268],{"class":58},"\"Authorization\"",[48,270,271],{"class":137},": ",[48,273,274],{"class":133},"f",[48,276,277],{"class":58},"\"Bearer ",[48,279,228],{"class":227},[48,281,282],{"class":137},"token",[48,284,233],{"class":227},[48,286,224],{"class":58},[48,288,289],{"class":137},"}, ",[48,291,292],{"class":260},"timeout",[48,294,218],{"class":133},[48,296,297],{"class":167},"30",[48,299,300],{"class":137},").json()\n",[48,302,304,307,309,312,314,317,320,323,326,329],{"class":50,"line":303},11,[48,305,306],{"class":137},"    previous ",[48,308,218],{"class":133},[48,310,311],{"class":137}," json.loads(",[48,313,180],{"class":167},[48,315,316],{"class":137},".read_text()) ",[48,318,319],{"class":133},"if",[48,321,322],{"class":167}," STATE",[48,324,325],{"class":137},".exists() ",[48,327,328],{"class":133},"else",[48,330,331],{"class":137}," {}\n",[48,333,335,338,340,343,346,349,352,355,358],{"class":50,"line":334},12,[48,336,337],{"class":137},"    changed ",[48,339,218],{"class":133},[48,341,342],{"class":137}," previous.get(item[",[48,344,345],{"class":58},"\"id\"",[48,347,348],{"class":137},"]) ",[48,350,351],{"class":133},"!=",[48,353,354],{"class":137}," item[",[48,356,357],{"class":58},"\"cTag\"",[48,359,360],{"class":137},"]\n",[48,362,364,367,369,372,374,376,378],{"class":50,"line":363},13,[48,365,366],{"class":137},"    previous[item[",[48,368,345],{"class":58},[48,370,371],{"class":137},"]] ",[48,373,218],{"class":133},[48,375,354],{"class":137},[48,377,357],{"class":58},[48,379,360],{"class":137},[48,381,383,386],{"class":50,"line":382},14,[48,384,385],{"class":167},"    STATE",[48,387,388],{"class":137},".write_text(json.dumps(previous))\n",[48,390,392,395],{"class":50,"line":391},15,[48,393,394],{"class":133},"    return",[48,396,397],{"class":137}," changed, item\n",[10,399,400,401,403,404,408,409,412],{},"The request selects only the fields it needs, so each poll is a tiny response. The last seen ",[45,402,113],{}," per item is stored in a small JSON file; the function reports a change when the tag differs. Run it every few minutes from cron or a scheduler — see ",[27,405,407],{"href":406},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Fchoose-a-scheduler-for-python-excel-reports\u002F","Choose a Scheduler for Python Excel Reports"," — and only rebuild the report when it returns ",[45,410,411],{},"True",". Polling one file every five minutes is 288 cheap requests a day, far below any throttling limit.",[38,414,416],{"className":124,"code":415,"language":126,"meta":43,"style":43},"import responses\n\nwith responses.RequestsMock() as mock:\n    url = f\"{GRAPH}\u002Fme\u002Fdrive\u002Froot:\u002FReports\u002Fforecast.xlsx?$select=id,name,cTag,lastModifiedDateTime\"\n    for ctag in (\"{ABC},5\", \"{ABC},5\", \"{ABC},6\"):\n        mock.get(url, json={\"id\": \"ITEM1\", \"name\": \"forecast.xlsx\", \"cTag\": f'\"c:{ctag}\"',\n                            \"lastModifiedDateTime\": \"2026-10-02T08:15:00Z\"})\n    STATE.unlink(missing_ok=True)\n    for run in range(3):\n        changed, item = content_changed(\"token\", \"Reports\u002Fforecast.xlsx\")\n        print(f\"poll {run + 1}: changed={changed} cTag={item['cTag']}\")\n",[45,417,418,424,428,442,461,504,557,570,586,606,626],{"__ignoreMap":43},[48,419,420,422],{"class":50,"line":51},[48,421,134],{"class":133},[48,423,71],{"class":137},[48,425,426],{"class":50,"line":141},[48,427,161],{"emptyLinePlaceholder":160},[48,429,430,433,436,439],{"class":50,"line":149},[48,431,432],{"class":133},"with",[48,434,435],{"class":137}," responses.RequestsMock() ",[48,437,438],{"class":133},"as",[48,440,441],{"class":137}," mock:\n",[48,443,444,446,448,450,452,454,456,458],{"class":50,"line":157},[48,445,215],{"class":137},[48,447,218],{"class":133},[48,449,221],{"class":133},[48,451,224],{"class":58},[48,453,228],{"class":227},[48,455,168],{"class":167},[48,457,233],{"class":227},[48,459,460],{"class":58},"\u002Fme\u002Fdrive\u002Froot:\u002FReports\u002Fforecast.xlsx?$select=id,name,cTag,lastModifiedDateTime\"\n",[48,462,463,466,469,472,475,477,480,483,486,488,490,492,494,496,498,501],{"class":50,"line":164},[48,464,465],{"class":133},"    for",[48,467,468],{"class":137}," ctag ",[48,470,471],{"class":133},"in",[48,473,474],{"class":137}," (",[48,476,224],{"class":58},[48,478,479],{"class":227},"{ABC}",[48,481,482],{"class":58},",5\"",[48,484,485],{"class":137},", ",[48,487,224],{"class":58},[48,489,479],{"class":227},[48,491,482],{"class":58},[48,493,485],{"class":137},[48,495,224],{"class":58},[48,497,479],{"class":227},[48,499,500],{"class":58},",6\"",[48,502,503],{"class":137},"):\n",[48,505,506,509,512,514,516,518,520,523,525,528,530,533,535,537,539,541,544,546,549,551,554],{"class":50,"line":177},[48,507,508],{"class":137},"        mock.get(url, ",[48,510,511],{"class":260},"json",[48,513,218],{"class":133},[48,515,228],{"class":137},[48,517,345],{"class":58},[48,519,271],{"class":137},[48,521,522],{"class":58},"\"ITEM1\"",[48,524,485],{"class":137},[48,526,527],{"class":58},"\"name\"",[48,529,271],{"class":137},[48,531,532],{"class":58},"\"forecast.xlsx\"",[48,534,485],{"class":137},[48,536,357],{"class":58},[48,538,271],{"class":137},[48,540,274],{"class":133},[48,542,543],{"class":58},"'\"c:",[48,545,228],{"class":227},[48,547,548],{"class":137},"ctag",[48,550,233],{"class":227},[48,552,553],{"class":58},"\"'",[48,555,556],{"class":137},",\n",[48,558,559,562,564,567],{"class":50,"line":194},[48,560,561],{"class":58},"                            \"lastModifiedDateTime\"",[48,563,271],{"class":137},[48,565,566],{"class":58},"\"2026-10-02T08:15:00Z\"",[48,568,569],{"class":137},"})\n",[48,571,572,574,577,580,582,584],{"class":50,"line":199},[48,573,385],{"class":167},[48,575,576],{"class":137},".unlink(",[48,578,579],{"class":260},"missing_ok",[48,581,218],{"class":133},[48,583,411],{"class":167},[48,585,191],{"class":137},[48,587,588,590,593,595,598,601,604],{"class":50,"line":212},[48,589,465],{"class":133},[48,591,592],{"class":137}," run ",[48,594,471],{"class":133},[48,596,597],{"class":167}," range",[48,599,600],{"class":137},"(",[48,602,603],{"class":167},"3",[48,605,503],{"class":137},[48,607,608,611,613,616,619,621,624],{"class":50,"line":249},[48,609,610],{"class":137},"        changed, item ",[48,612,218],{"class":133},[48,614,615],{"class":137}," content_changed(",[48,617,618],{"class":58},"\"token\"",[48,620,485],{"class":137},[48,622,623],{"class":58},"\"Reports\u002Fforecast.xlsx\"",[48,625,191],{"class":137},[48,627,628,631,633,635,638,640,643,646,649,651,654,656,659,661,664,666,669,672,675,677,679],{"class":50,"line":303},[48,629,630],{"class":167},"        print",[48,632,600],{"class":137},[48,634,274],{"class":133},[48,636,637],{"class":58},"\"poll ",[48,639,228],{"class":227},[48,641,642],{"class":137},"run ",[48,644,645],{"class":133},"+",[48,647,648],{"class":167}," 1",[48,650,233],{"class":227},[48,652,653],{"class":58},": changed=",[48,655,228],{"class":227},[48,657,658],{"class":137},"changed",[48,660,233],{"class":227},[48,662,663],{"class":58}," cTag=",[48,665,228],{"class":227},[48,667,668],{"class":137},"item[",[48,670,671],{"class":58},"'cTag'",[48,673,674],{"class":137},"]",[48,676,233],{"class":227},[48,678,224],{"class":58},[48,680,191],{"class":137},[10,682,683],{},"The first poll always counts as a change (nothing was stored yet), the second sees the same tag and does nothing, and the third sees a new tag and triggers the report.",[685,686,694,695,694,699,694,703,694,711,694,721,694,732,694,738,694,744,694,750,694,757,694,761,694,766,694,770,694,777,694,782,694,789,694,793,694,797,694,801,694,806,694,809,694,814,694,817],"svg",{"viewBox":687,"role":688,"ariaLabelledBy":689,"xmlns":692,"style":693},"4 0 752 207","img",[690,691],"od-tags-t","od-tags-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[696,697,698],"title",{"id":690},"eTag and cTag on a drive item",[700,701,702],"desc",{"id":691},"The eTag changes on any change to the item, including renames, moves and sharing changes, so it can trigger reports needlessly. The cTag changes only when the file content changes, which is what a report cares about.",[704,705],"rect",{"x":706,"y":707,"width":708,"height":709,"fill":710},"4","0","752","207","#ffffff",[704,712],{"x":713,"y":714,"width":715,"height":716,"rx":717,"fill":718,"stroke":719,"style":720},"20.0","16.0","351.0","175.0","14","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[722,723,725],"g",{"transform":724},"translate(182.5,29.0) scale(1.083)",[726,727],"path",{"d":728,"fill":729,"stroke":730,"style":731},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[733,734,105],"text",{"x":735,"y":736,"style":737},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[50,739],{"x1":740,"y1":741,"x2":742,"y2":741,"stroke":719,"style":743},"36.0","91.0","355.0","stroke-width:1px",[733,745,749],{"x":746,"y":747,"style":748},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","changes on any update",[722,751,753],{"transform":752},"translate(38.5,99.5) scale(0.625)",[726,754],{"d":755,"fill":729,"stroke":719,"style":756},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[733,758,760],{"x":746,"y":759,"style":748},"137.0","rename, move, sharing, content",[722,762,764],{"transform":763},"translate(38.5,125.5) scale(0.625)",[726,765],{"d":755,"fill":729,"stroke":719,"style":756},[733,767,769],{"x":746,"y":768,"style":748},"163.0","triggers reports needlessly",[722,771,773],{"transform":772},"translate(38.5,151.5) scale(0.625)",[726,774],{"d":775,"fill":729,"stroke":776,"style":756},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[704,778],{"x":779,"y":714,"width":715,"height":716,"rx":717,"fill":780,"stroke":781,"style":720},"389.0","#d9f4f1","var(--teal,#0f9488)",[722,783,785],{"transform":784},"translate(551.5,29.0) scale(1.083)",[726,786],{"d":787,"fill":729,"stroke":788,"style":731},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[733,790,113],{"x":791,"y":736,"style":792},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[50,794],{"x1":795,"y1":741,"x2":796,"y2":741,"stroke":781,"style":743},"405.0","724.0",[733,798,800],{"x":799,"y":747,"style":748},"429.0","changes only with content",[722,802,804],{"transform":803},"translate(407.5,99.5) scale(0.625)",[726,805],{"d":787,"fill":729,"stroke":781,"style":756},[733,807,808],{"x":799,"y":759,"style":748},"ignores renames and sharing",[722,810,812],{"transform":811},"translate(407.5,125.5) scale(0.625)",[726,813],{"d":787,"fill":729,"stroke":781,"style":756},[733,815,816],{"x":799,"y":768,"style":748},"right trigger for a report",[722,818,820],{"transform":819},"translate(407.5,151.5) scale(0.625)",[726,821],{"d":787,"fill":729,"stroke":781,"style":756},[33,823,825],{"id":824},"step-2-watch-a-whole-folder-with-the-delta-query","Step 2 — Watch a whole folder with the delta query",[10,827,828,829,832],{},"When the inputs are many workbooks in a folder, polling each one is wasteful. The delta endpoint returns everything that changed since your last call, and a ",[45,830,831],{},"deltaLink"," to use next time:",[38,834,836],{"className":124,"code":835,"language":126,"meta":43,"style":43},"def changed_workbooks(token, folder_id, delta_link=None):\n    url = delta_link or f\"{GRAPH}\u002Fme\u002Fdrive\u002Fitems\u002F{folder_id}\u002Fdelta?$select=id,name,file,deleted,cTag\"\n    changed = []\n    while url:\n        page = requests.get(url, headers={\"Authorization\": f\"Bearer {token}\"}, timeout=30).json()\n        for item in page.get(\"value\", []):\n            if \"deleted\" in item or not item.get(\"name\", \"\").lower().endswith(\".xlsx\"):\n                continue\n            changed.append(item[\"name\"])\n        url = page.get(\"@odata.nextLink\")\n        delta_link = page.get(\"@odata.deltaLink\", delta_link)\n    return changed, delta_link\n\nFOLDER = \"FOLDER1\"\nfirst_url = f\"{GRAPH}\u002Fme\u002Fdrive\u002Fitems\u002F{FOLDER}\u002Fdelta?$select=id,name,file,deleted,cTag\"\nwith responses.RequestsMock() as mock:\n    mock.get(first_url, json={\"value\": [{\"id\": \"1\", \"name\": \"north.xlsx\", \"file\": {}},\n                                        {\"id\": \"2\", \"name\": \"notes.txt\", \"file\": {}}],\n                              \"@odata.nextLink\": f\"{GRAPH}\u002Fdelta-page-2\"})\n    mock.get(f\"{GRAPH}\u002Fdelta-page-2\", json={\"value\": [{\"id\": \"3\", \"name\": \"south.xlsx\", \"file\": {}}],\n                                           \"@odata.deltaLink\": f\"{GRAPH}\u002Fdelta?token=xyz\"})\n    mock.get(f\"{GRAPH}\u002Fdelta?token=xyz\", json={\"value\": [{\"id\": \"3\", \"name\": \"south.xlsx\", \"file\": {}}],\n                                               \"@odata.deltaLink\": f\"{GRAPH}\u002Fdelta?token=xyz2\"})\n    names, link = changed_workbooks(\"token\", FOLDER)\n    print(\"first sync:\", names, \"| next link:\", link)\n    names, link = changed_workbooks(\"token\", FOLDER, link)\n    print(\"second sync:\", names, \"| next link:\", link)\n",[45,837,838,855,890,899,907,948,967,1003,1008,1018,1032,1047,1054,1058,1068,1095,1106,1147,1176,1199,1251,1274,1323,1346,1365,1385,1402],{"__ignoreMap":43},[48,839,840,842,845,848,850,853],{"class":50,"line":51},[48,841,202],{"class":133},[48,843,844],{"class":205}," changed_workbooks",[48,846,847],{"class":137},"(token, folder_id, delta_link",[48,849,218],{"class":133},[48,851,852],{"class":167},"None",[48,854,503],{"class":137},[48,856,857,859,861,864,867,869,871,873,875,877,880,882,885,887],{"class":50,"line":141},[48,858,215],{"class":137},[48,860,218],{"class":133},[48,862,863],{"class":137}," delta_link ",[48,865,866],{"class":133},"or",[48,868,221],{"class":133},[48,870,224],{"class":58},[48,872,228],{"class":227},[48,874,168],{"class":167},[48,876,233],{"class":227},[48,878,879],{"class":58},"\u002Fme\u002Fdrive\u002Fitems\u002F",[48,881,228],{"class":227},[48,883,884],{"class":137},"folder_id",[48,886,233],{"class":227},[48,888,889],{"class":58},"\u002Fdelta?$select=id,name,file,deleted,cTag\"\n",[48,891,892,894,896],{"class":50,"line":149},[48,893,337],{"class":137},[48,895,218],{"class":133},[48,897,898],{"class":137}," []\n",[48,900,901,904],{"class":50,"line":157},[48,902,903],{"class":133},"    while",[48,905,906],{"class":137}," url:\n",[48,908,909,912,914,916,918,920,922,924,926,928,930,932,934,936,938,940,942,944,946],{"class":50,"line":164},[48,910,911],{"class":137},"        page ",[48,913,218],{"class":133},[48,915,257],{"class":137},[48,917,261],{"class":260},[48,919,218],{"class":133},[48,921,228],{"class":137},[48,923,268],{"class":58},[48,925,271],{"class":137},[48,927,274],{"class":133},[48,929,277],{"class":58},[48,931,228],{"class":227},[48,933,282],{"class":137},[48,935,233],{"class":227},[48,937,224],{"class":58},[48,939,289],{"class":137},[48,941,292],{"class":260},[48,943,218],{"class":133},[48,945,297],{"class":167},[48,947,300],{"class":137},[48,949,950,953,956,958,961,964],{"class":50,"line":177},[48,951,952],{"class":133},"        for",[48,954,955],{"class":137}," item ",[48,957,471],{"class":133},[48,959,960],{"class":137}," page.get(",[48,962,963],{"class":58},"\"value\"",[48,965,966],{"class":137},", []):\n",[48,968,969,972,975,978,980,982,985,988,990,992,995,998,1001],{"class":50,"line":194},[48,970,971],{"class":133},"            if",[48,973,974],{"class":58}," \"deleted\"",[48,976,977],{"class":133}," in",[48,979,955],{"class":137},[48,981,866],{"class":133},[48,983,984],{"class":133}," not",[48,986,987],{"class":137}," item.get(",[48,989,527],{"class":58},[48,991,485],{"class":137},[48,993,994],{"class":58},"\"\"",[48,996,997],{"class":137},").lower().endswith(",[48,999,1000],{"class":58},"\".xlsx\"",[48,1002,503],{"class":137},[48,1004,1005],{"class":50,"line":199},[48,1006,1007],{"class":133},"                continue\n",[48,1009,1010,1013,1015],{"class":50,"line":212},[48,1011,1012],{"class":137},"            changed.append(item[",[48,1014,527],{"class":58},[48,1016,1017],{"class":137},"])\n",[48,1019,1020,1023,1025,1027,1030],{"class":50,"line":249},[48,1021,1022],{"class":137},"        url ",[48,1024,218],{"class":133},[48,1026,960],{"class":137},[48,1028,1029],{"class":58},"\"@odata.nextLink\"",[48,1031,191],{"class":137},[48,1033,1034,1037,1039,1041,1044],{"class":50,"line":303},[48,1035,1036],{"class":137},"        delta_link ",[48,1038,218],{"class":133},[48,1040,960],{"class":137},[48,1042,1043],{"class":58},"\"@odata.deltaLink\"",[48,1045,1046],{"class":137},", delta_link)\n",[48,1048,1049,1051],{"class":50,"line":334},[48,1050,394],{"class":133},[48,1052,1053],{"class":137}," changed, delta_link\n",[48,1055,1056],{"class":50,"line":363},[48,1057,161],{"emptyLinePlaceholder":160},[48,1059,1060,1063,1065],{"class":50,"line":382},[48,1061,1062],{"class":167},"FOLDER",[48,1064,171],{"class":133},[48,1066,1067],{"class":58}," \"FOLDER1\"\n",[48,1069,1070,1073,1075,1077,1079,1081,1083,1085,1087,1089,1091,1093],{"class":50,"line":391},[48,1071,1072],{"class":137},"first_url ",[48,1074,218],{"class":133},[48,1076,221],{"class":133},[48,1078,224],{"class":58},[48,1080,228],{"class":227},[48,1082,168],{"class":167},[48,1084,233],{"class":227},[48,1086,879],{"class":58},[48,1088,228],{"class":227},[48,1090,1062],{"class":167},[48,1092,233],{"class":227},[48,1094,889],{"class":58},[48,1096,1098,1100,1102,1104],{"class":50,"line":1097},16,[48,1099,432],{"class":133},[48,1101,435],{"class":137},[48,1103,438],{"class":133},[48,1105,441],{"class":137},[48,1107,1109,1112,1114,1116,1118,1120,1123,1125,1127,1130,1132,1134,1136,1139,1141,1144],{"class":50,"line":1108},17,[48,1110,1111],{"class":137},"    mock.get(first_url, ",[48,1113,511],{"class":260},[48,1115,218],{"class":133},[48,1117,228],{"class":137},[48,1119,963],{"class":58},[48,1121,1122],{"class":137},": [{",[48,1124,345],{"class":58},[48,1126,271],{"class":137},[48,1128,1129],{"class":58},"\"1\"",[48,1131,485],{"class":137},[48,1133,527],{"class":58},[48,1135,271],{"class":137},[48,1137,1138],{"class":58},"\"north.xlsx\"",[48,1140,485],{"class":137},[48,1142,1143],{"class":58},"\"file\"",[48,1145,1146],{"class":137},": {}},\n",[48,1148,1150,1153,1155,1157,1160,1162,1164,1166,1169,1171,1173],{"class":50,"line":1149},18,[48,1151,1152],{"class":137},"                                        {",[48,1154,345],{"class":58},[48,1156,271],{"class":137},[48,1158,1159],{"class":58},"\"2\"",[48,1161,485],{"class":137},[48,1163,527],{"class":58},[48,1165,271],{"class":137},[48,1167,1168],{"class":58},"\"notes.txt\"",[48,1170,485],{"class":137},[48,1172,1143],{"class":58},[48,1174,1175],{"class":137},": {}}],\n",[48,1177,1179,1182,1184,1186,1188,1190,1192,1194,1197],{"class":50,"line":1178},19,[48,1180,1181],{"class":58},"                              \"@odata.nextLink\"",[48,1183,271],{"class":137},[48,1185,274],{"class":133},[48,1187,224],{"class":58},[48,1189,228],{"class":227},[48,1191,168],{"class":167},[48,1193,233],{"class":227},[48,1195,1196],{"class":58},"\u002Fdelta-page-2\"",[48,1198,569],{"class":137},[48,1200,1202,1205,1207,1209,1211,1213,1215,1217,1219,1221,1223,1225,1227,1229,1231,1233,1236,1238,1240,1242,1245,1247,1249],{"class":50,"line":1201},20,[48,1203,1204],{"class":137},"    mock.get(",[48,1206,274],{"class":133},[48,1208,224],{"class":58},[48,1210,228],{"class":227},[48,1212,168],{"class":167},[48,1214,233],{"class":227},[48,1216,1196],{"class":58},[48,1218,485],{"class":137},[48,1220,511],{"class":260},[48,1222,218],{"class":133},[48,1224,228],{"class":137},[48,1226,963],{"class":58},[48,1228,1122],{"class":137},[48,1230,345],{"class":58},[48,1232,271],{"class":137},[48,1234,1235],{"class":58},"\"3\"",[48,1237,485],{"class":137},[48,1239,527],{"class":58},[48,1241,271],{"class":137},[48,1243,1244],{"class":58},"\"south.xlsx\"",[48,1246,485],{"class":137},[48,1248,1143],{"class":58},[48,1250,1175],{"class":137},[48,1252,1254,1257,1259,1261,1263,1265,1267,1269,1272],{"class":50,"line":1253},21,[48,1255,1256],{"class":58},"                                           \"@odata.deltaLink\"",[48,1258,271],{"class":137},[48,1260,274],{"class":133},[48,1262,224],{"class":58},[48,1264,228],{"class":227},[48,1266,168],{"class":167},[48,1268,233],{"class":227},[48,1270,1271],{"class":58},"\u002Fdelta?token=xyz\"",[48,1273,569],{"class":137},[48,1275,1277,1279,1281,1283,1285,1287,1289,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309,1311,1313,1315,1317,1319,1321],{"class":50,"line":1276},22,[48,1278,1204],{"class":137},[48,1280,274],{"class":133},[48,1282,224],{"class":58},[48,1284,228],{"class":227},[48,1286,168],{"class":167},[48,1288,233],{"class":227},[48,1290,1271],{"class":58},[48,1292,485],{"class":137},[48,1294,511],{"class":260},[48,1296,218],{"class":133},[48,1298,228],{"class":137},[48,1300,963],{"class":58},[48,1302,1122],{"class":137},[48,1304,345],{"class":58},[48,1306,271],{"class":137},[48,1308,1235],{"class":58},[48,1310,485],{"class":137},[48,1312,527],{"class":58},[48,1314,271],{"class":137},[48,1316,1244],{"class":58},[48,1318,485],{"class":137},[48,1320,1143],{"class":58},[48,1322,1175],{"class":137},[48,1324,1326,1329,1331,1333,1335,1337,1339,1341,1344],{"class":50,"line":1325},23,[48,1327,1328],{"class":58},"                                               \"@odata.deltaLink\"",[48,1330,271],{"class":137},[48,1332,274],{"class":133},[48,1334,224],{"class":58},[48,1336,228],{"class":227},[48,1338,168],{"class":167},[48,1340,233],{"class":227},[48,1342,1343],{"class":58},"\u002Fdelta?token=xyz2\"",[48,1345,569],{"class":137},[48,1347,1349,1352,1354,1357,1359,1361,1363],{"class":50,"line":1348},24,[48,1350,1351],{"class":137},"    names, link ",[48,1353,218],{"class":133},[48,1355,1356],{"class":137}," changed_workbooks(",[48,1358,618],{"class":58},[48,1360,485],{"class":137},[48,1362,1062],{"class":167},[48,1364,191],{"class":137},[48,1366,1368,1371,1373,1376,1379,1382],{"class":50,"line":1367},25,[48,1369,1370],{"class":167},"    print",[48,1372,600],{"class":137},[48,1374,1375],{"class":58},"\"first sync:\"",[48,1377,1378],{"class":137},", names, ",[48,1380,1381],{"class":58},"\"| next link:\"",[48,1383,1384],{"class":137},", link)\n",[48,1386,1388,1390,1392,1394,1396,1398,1400],{"class":50,"line":1387},26,[48,1389,1351],{"class":137},[48,1391,218],{"class":133},[48,1393,1356],{"class":137},[48,1395,618],{"class":58},[48,1397,485],{"class":137},[48,1399,1062],{"class":167},[48,1401,1384],{"class":137},[48,1403,1405,1407,1409,1412,1414,1416],{"class":50,"line":1404},27,[48,1406,1370],{"class":167},[48,1408,600],{"class":137},[48,1410,1411],{"class":58},"\"second sync:\"",[48,1413,1378],{"class":137},[48,1415,1381],{"class":58},[48,1417,1384],{"class":137},[10,1419,1420,1421,1424,1425,1428,1429,1432],{},"The first call enumerates the folder's current contents across as many pages as needed (",[45,1422,1423],{},"@odata.nextLink",") and ends with a ",[45,1426,1427],{},"@odata.deltaLink","; store that link, and the next call returns only what changed since — here just ",[45,1430,1431],{},"south.xlsx",". Deleted items and non-Excel files are skipped. Keep the delta link somewhere durable (a file, a database row); if it is lost, start again with the initial call.",[685,1434,694,1439,694,1442,694,1445,694,1448,694,1455,694,1461,694,1466,694,1471,694,1474,694,1478,694,1483,694,1485,694,1487,694,1492,694,1497,694,1505,694,1511,694,1516,694,1519,694,1524,694,1528,694,1532,694,1535,694,1540,694,1545,694,1548,694,1551,694,1556,694,1560,694,1563,694,1566,694,1571],{"viewBox":1435,"role":688,"ariaLabelledBy":1436,"xmlns":692,"style":693},"4 0 752 340",[1437,1438],"od-delta-t","od-delta-d",[696,1440,1441],{"id":1437},"How the delta query keeps a folder in sync",[700,1443,1444],{"id":1438},"The first delta call returns all items over several pages and ends with a deltaLink. The script stores the link. On the next run it calls the deltaLink and receives only items changed since, plus a new deltaLink.",[704,1446],{"x":706,"y":707,"width":708,"height":1447,"fill":710},"340",[704,1449],{"x":713,"y":714,"width":1450,"height":1451,"rx":1452,"fill":1453,"stroke":1454,"style":720},"180.0","48.0","12","#f0f4ff","var(--brand,#5b5cf0)",[733,1456,1460],{"x":1457,"y":1458,"style":1459},"110.0","44.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Report job",[704,1462],{"x":1463,"y":714,"width":1450,"height":1451,"rx":1452,"fill":1464,"stroke":1465,"style":720},"290.0","#e7ebef","var(--line,#cdd5e6)",[733,1467,1470],{"x":1468,"y":1458,"style":1469},"380.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Graph delta",[704,1472],{"x":1473,"y":714,"width":1450,"height":1451,"rx":1452,"fill":1464,"stroke":1465,"style":720},"560.0",[733,1475,1477],{"x":1476,"y":1458,"style":1469},"650.0","Link store",[50,1479],{"x1":1457,"y1":1480,"x2":1457,"y2":1481,"stroke":1465,"style":1482},"64.0","324.0","stroke-width:1.5px;stroke-dasharray:5 4",[50,1484],{"x1":1468,"y1":1480,"x2":1468,"y2":1481,"stroke":1465,"style":1482},[50,1486],{"x1":1476,"y1":1480,"x2":1476,"y2":1481,"stroke":1465,"style":1482},[50,1488],{"x1":1489,"y1":1490,"x2":1491,"y2":1490,"stroke":1454,"style":720},"115.0","104.0","371.0",[1493,1494],"polygon",{"points":1495,"fill":1496},"375.0,104.0 366.0,109.0 366.0,99.0","#5b5cf0",[704,1498],{"x":1499,"y":1500,"width":1501,"height":1502,"rx":1503,"fill":710,"stroke":1465,"style":1504},"159.3","75.0","171.4","21.0","10","stroke-width:1.2px",[733,1506,1510],{"x":1507,"y":1508,"style":1509},"245.0","89.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. GET …\u002Fdelta (first sync)",[50,1512],{"x1":1513,"y1":1514,"x2":1515,"y2":1514,"stroke":1454,"style":720},"375.0","152.0","119.0",[1493,1517],{"points":1518,"fill":1496},"115.0,152.0 124.0,147.0 124.0,157.0",[704,1520],{"x":1521,"y":1522,"width":1523,"height":1502,"rx":1503,"fill":710,"stroke":1465,"style":1504},"152.3","123.0","185.4",[733,1525,1527],{"x":1507,"y":1526,"style":1509},"137.5","2. all items + nextLink pages",[50,1529],{"x1":1489,"y1":1530,"x2":1531,"y2":1530,"stroke":1454,"style":720},"200.0","641.0",[1493,1533],{"points":1534,"fill":1496},"645.0,200.0 636.0,205.0 636.0,195.0",[704,1536],{"x":1537,"y":1538,"width":1539,"height":1502,"rx":1503,"fill":1453,"stroke":1454,"style":1504},"321.0","171.0","117.9",[733,1541,1544],{"x":1468,"y":1542,"style":1543},"185.5","font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","3. save deltaLink",[50,1546],{"x1":1489,"y1":1547,"x2":1491,"y2":1547,"stroke":1454,"style":720},"248.0",[1493,1549],{"points":1550,"fill":1496},"375.0,248.0 366.0,253.0 366.0,243.0",[704,1552],{"x":1553,"y":1554,"width":1555,"height":1502,"rx":1503,"fill":710,"stroke":1465,"style":1504},"157.3","219.0","175.4",[733,1557,1559],{"x":1507,"y":1558,"style":1509},"233.5","4. GET deltaLink (next run)",[50,1561],{"x1":1513,"y1":1562,"x2":1515,"y2":1562,"stroke":1454,"style":720},"296.0",[1493,1564],{"points":1565,"fill":1496},"115.0,296.0 124.0,291.0 124.0,301.0",[704,1567],{"x":1568,"y":1569,"width":1570,"height":1502,"rx":1503,"fill":780,"stroke":781,"style":1504},"123.1","267.0","243.8",[733,1572,1575],{"x":1507,"y":1573,"style":1574},"281.5","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","5. only changed items + new deltaLink",[33,1577,1579],{"id":1578},"step-3-get-notified-with-a-webhook","Step 3 — Get notified with a webhook",[10,1581,1582,1583,1586],{},"For near-immediate reactions, create a subscription and Graph will POST to your endpoint when the drive changes. Your service must answer a validation handshake: when the subscription is created, Graph calls the URL with a ",[45,1584,1585],{},"validationToken"," query parameter, and the endpoint must return that token as plain text within ten seconds:",[38,1588,1590],{"className":124,"code":1589,"language":126,"meta":43,"style":43},"from flask import Flask, request\n\napp = Flask(__name__)\npending = []\n\n@app.post(\"\u002Fgraph-notifications\")\ndef notifications():\n    token = request.args.get(\"validationToken\")\n    if token:\n        return token, 200, {\"Content-Type\": \"text\u002Fplain\"}\n    for note in request.get_json().get(\"value\", []):\n        if note.get(\"clientState\") == \"expected-secret\":\n            pending.append(note[\"resource\"])\n    return \"\", 202\n\nclient = app.test_client()\nr = client.post(\"\u002Fgraph-notifications?validationToken=abc123\")\nprint(\"validation:\", r.status_code, r.data.decode())\nr = client.post(\"\u002Fgraph-notifications\", json={\"value\": [\n    {\"subscriptionId\": \"s1\", \"clientState\": \"expected-secret\", \"resource\": \"me\u002Fdrive\u002Froot\", \"changeType\": \"updated\"},\n    {\"subscriptionId\": \"s2\", \"clientState\": \"wrong\", \"resource\": \"me\u002Fdrive\u002Froot\", \"changeType\": \"updated\"}]})\nprint(\"notification:\", r.status_code, pending)\n",[45,1591,1592,1605,1609,1624,1633,1637,1649,1659,1674,1682,1707,1723,1746,1756,1768,1772,1782,1797,1810,1833,1877,1916],{"__ignoreMap":43},[48,1593,1594,1597,1600,1602],{"class":50,"line":51},[48,1595,1596],{"class":133},"from",[48,1598,1599],{"class":137}," flask ",[48,1601,134],{"class":133},[48,1603,1604],{"class":137}," Flask, request\n",[48,1606,1607],{"class":50,"line":141},[48,1608,161],{"emptyLinePlaceholder":160},[48,1610,1611,1614,1616,1619,1622],{"class":50,"line":149},[48,1612,1613],{"class":137},"app ",[48,1615,218],{"class":133},[48,1617,1618],{"class":137}," Flask(",[48,1620,1621],{"class":167},"__name__",[48,1623,191],{"class":137},[48,1625,1626,1629,1631],{"class":50,"line":157},[48,1627,1628],{"class":137},"pending ",[48,1630,218],{"class":133},[48,1632,898],{"class":137},[48,1634,1635],{"class":50,"line":164},[48,1636,161],{"emptyLinePlaceholder":160},[48,1638,1639,1642,1644,1647],{"class":50,"line":177},[48,1640,1641],{"class":205},"@app.post",[48,1643,600],{"class":137},[48,1645,1646],{"class":58},"\"\u002Fgraph-notifications\"",[48,1648,191],{"class":137},[48,1650,1651,1653,1656],{"class":50,"line":194},[48,1652,202],{"class":133},[48,1654,1655],{"class":205}," notifications",[48,1657,1658],{"class":137},"():\n",[48,1660,1661,1664,1666,1669,1672],{"class":50,"line":199},[48,1662,1663],{"class":137},"    token ",[48,1665,218],{"class":133},[48,1667,1668],{"class":137}," request.args.get(",[48,1670,1671],{"class":58},"\"validationToken\"",[48,1673,191],{"class":137},[48,1675,1676,1679],{"class":50,"line":212},[48,1677,1678],{"class":133},"    if",[48,1680,1681],{"class":137}," token:\n",[48,1683,1684,1687,1690,1693,1696,1699,1701,1704],{"class":50,"line":249},[48,1685,1686],{"class":133},"        return",[48,1688,1689],{"class":137}," token, ",[48,1691,1692],{"class":167},"200",[48,1694,1695],{"class":137},", {",[48,1697,1698],{"class":58},"\"Content-Type\"",[48,1700,271],{"class":137},[48,1702,1703],{"class":58},"\"text\u002Fplain\"",[48,1705,1706],{"class":137},"}\n",[48,1708,1709,1711,1714,1716,1719,1721],{"class":50,"line":303},[48,1710,465],{"class":133},[48,1712,1713],{"class":137}," note ",[48,1715,471],{"class":133},[48,1717,1718],{"class":137}," request.get_json().get(",[48,1720,963],{"class":58},[48,1722,966],{"class":137},[48,1724,1725,1728,1731,1734,1737,1740,1743],{"class":50,"line":334},[48,1726,1727],{"class":133},"        if",[48,1729,1730],{"class":137}," note.get(",[48,1732,1733],{"class":58},"\"clientState\"",[48,1735,1736],{"class":137},") ",[48,1738,1739],{"class":133},"==",[48,1741,1742],{"class":58}," \"expected-secret\"",[48,1744,1745],{"class":137},":\n",[48,1747,1748,1751,1754],{"class":50,"line":363},[48,1749,1750],{"class":137},"            pending.append(note[",[48,1752,1753],{"class":58},"\"resource\"",[48,1755,1017],{"class":137},[48,1757,1758,1760,1763,1765],{"class":50,"line":382},[48,1759,394],{"class":133},[48,1761,1762],{"class":58}," \"\"",[48,1764,485],{"class":137},[48,1766,1767],{"class":167},"202\n",[48,1769,1770],{"class":50,"line":391},[48,1771,161],{"emptyLinePlaceholder":160},[48,1773,1774,1777,1779],{"class":50,"line":1097},[48,1775,1776],{"class":137},"client ",[48,1778,218],{"class":133},[48,1780,1781],{"class":137}," app.test_client()\n",[48,1783,1784,1787,1789,1792,1795],{"class":50,"line":1108},[48,1785,1786],{"class":137},"r ",[48,1788,218],{"class":133},[48,1790,1791],{"class":137}," client.post(",[48,1793,1794],{"class":58},"\"\u002Fgraph-notifications?validationToken=abc123\"",[48,1796,191],{"class":137},[48,1798,1799,1802,1804,1807],{"class":50,"line":1149},[48,1800,1801],{"class":167},"print",[48,1803,600],{"class":137},[48,1805,1806],{"class":58},"\"validation:\"",[48,1808,1809],{"class":137},", r.status_code, r.data.decode())\n",[48,1811,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830],{"class":50,"line":1178},[48,1813,1786],{"class":137},[48,1815,218],{"class":133},[48,1817,1791],{"class":137},[48,1819,1646],{"class":58},[48,1821,485],{"class":137},[48,1823,511],{"class":260},[48,1825,218],{"class":133},[48,1827,228],{"class":137},[48,1829,963],{"class":58},[48,1831,1832],{"class":137},": [\n",[48,1834,1835,1838,1841,1843,1846,1848,1850,1852,1855,1857,1859,1861,1864,1866,1869,1871,1874],{"class":50,"line":1201},[48,1836,1837],{"class":137},"    {",[48,1839,1840],{"class":58},"\"subscriptionId\"",[48,1842,271],{"class":137},[48,1844,1845],{"class":58},"\"s1\"",[48,1847,485],{"class":137},[48,1849,1733],{"class":58},[48,1851,271],{"class":137},[48,1853,1854],{"class":58},"\"expected-secret\"",[48,1856,485],{"class":137},[48,1858,1753],{"class":58},[48,1860,271],{"class":137},[48,1862,1863],{"class":58},"\"me\u002Fdrive\u002Froot\"",[48,1865,485],{"class":137},[48,1867,1868],{"class":58},"\"changeType\"",[48,1870,271],{"class":137},[48,1872,1873],{"class":58},"\"updated\"",[48,1875,1876],{"class":137},"},\n",[48,1878,1879,1881,1883,1885,1888,1890,1892,1894,1897,1899,1901,1903,1905,1907,1909,1911,1913],{"class":50,"line":1253},[48,1880,1837],{"class":137},[48,1882,1840],{"class":58},[48,1884,271],{"class":137},[48,1886,1887],{"class":58},"\"s2\"",[48,1889,485],{"class":137},[48,1891,1733],{"class":58},[48,1893,271],{"class":137},[48,1895,1896],{"class":58},"\"wrong\"",[48,1898,485],{"class":137},[48,1900,1753],{"class":58},[48,1902,271],{"class":137},[48,1904,1863],{"class":58},[48,1906,485],{"class":137},[48,1908,1868],{"class":58},[48,1910,271],{"class":137},[48,1912,1873],{"class":58},[48,1914,1915],{"class":137},"}]})\n",[48,1917,1918,1920,1922,1925],{"class":50,"line":1276},[48,1919,1801],{"class":167},[48,1921,600],{"class":137},[48,1923,1924],{"class":58},"\"notification:\"",[48,1926,1927],{"class":137},", r.status_code, pending)\n",[10,1929,1930,1931,1934,1935,1938,1939,1942,1943,1946,1947,1950,1951,1954,1955,1958,1959,1961],{},"The handler echoes the validation token, then accepts notifications with ",[45,1932,1933],{},"202"," — quickly, because Graph expects an answer within a few seconds and retries otherwise. The ",[45,1936,1937],{},"clientState"," value, a secret you choose when creating the subscription, is checked to reject forged calls; the second notification is ignored. Notifications for drive items say only ",[14,1940,1941],{},"that"," something changed, not what — the usual pattern is to run the delta query from Step 2 when a notification arrives. The subscription itself is created with a POST to ",[45,1944,1945],{},"\u002Fsubscriptions"," (resource ",[45,1948,1949],{},"\u002Fme\u002Fdrive\u002Froot",", change type ",[45,1952,1953],{},"updated",", your ",[45,1956,1957],{},"notificationUrl"," and ",[45,1960,1937],{},", and an expiration date) and must be renewed before it expires; drive subscriptions last at most about 29 days, so a daily renewal job is a safe habit.",[33,1963,1965],{"id":1964},"step-4-debounce-the-autosaves","Step 4 — Debounce the autosaves",[10,1967,1968,1969,1971],{},"Excel for the web saves continuously while someone types. Each save changes the ",[45,1970,113],{}," and can fire a notification. Wait until the file has been quiet for a while before running the report:",[38,1973,1975],{"className":124,"code":1974,"language":126,"meta":43,"style":43},"import datetime as dt\n\nQUIET = dt.timedelta(minutes=5)\n\ndef ready_to_run(last_modified_iso, now):\n    last_modified = dt.datetime.fromisoformat(last_modified_iso.replace(\"Z\", \"+00:00\"))\n    return now - last_modified >= QUIET\n\nnow = dt.datetime(2026, 10, 2, 8, 18, tzinfo=dt.timezone.utc)\nfor stamp in (\"2026-10-02T08:15:00Z\", \"2026-10-02T08:10:00Z\"):\n    print(stamp, \"→ run now\" if ready_to_run(stamp, now) else \"→ wait, still being edited\")\n",[45,1976,1977,1989,1993,2013,2017,2027,2048,2067,2071,2113,2134],{"__ignoreMap":43},[48,1978,1979,1981,1984,1986],{"class":50,"line":51},[48,1980,134],{"class":133},[48,1982,1983],{"class":137}," datetime ",[48,1985,438],{"class":133},[48,1987,1988],{"class":137}," dt\n",[48,1990,1991],{"class":50,"line":141},[48,1992,161],{"emptyLinePlaceholder":160},[48,1994,1995,1998,2000,2003,2006,2008,2011],{"class":50,"line":149},[48,1996,1997],{"class":167},"QUIET",[48,1999,171],{"class":133},[48,2001,2002],{"class":137}," dt.timedelta(",[48,2004,2005],{"class":260},"minutes",[48,2007,218],{"class":133},[48,2009,2010],{"class":167},"5",[48,2012,191],{"class":137},[48,2014,2015],{"class":50,"line":157},[48,2016,161],{"emptyLinePlaceholder":160},[48,2018,2019,2021,2024],{"class":50,"line":164},[48,2020,202],{"class":133},[48,2022,2023],{"class":205}," ready_to_run",[48,2025,2026],{"class":137},"(last_modified_iso, now):\n",[48,2028,2029,2032,2034,2037,2040,2042,2045],{"class":50,"line":177},[48,2030,2031],{"class":137},"    last_modified ",[48,2033,218],{"class":133},[48,2035,2036],{"class":137}," dt.datetime.fromisoformat(last_modified_iso.replace(",[48,2038,2039],{"class":58},"\"Z\"",[48,2041,485],{"class":137},[48,2043,2044],{"class":58},"\"+00:00\"",[48,2046,2047],{"class":137},"))\n",[48,2049,2050,2052,2055,2058,2061,2064],{"class":50,"line":194},[48,2051,394],{"class":133},[48,2053,2054],{"class":137}," now ",[48,2056,2057],{"class":133},"-",[48,2059,2060],{"class":137}," last_modified ",[48,2062,2063],{"class":133},">=",[48,2065,2066],{"class":167}," QUIET\n",[48,2068,2069],{"class":50,"line":199},[48,2070,161],{"emptyLinePlaceholder":160},[48,2072,2073,2076,2078,2081,2084,2086,2088,2090,2093,2095,2098,2100,2103,2105,2108,2110],{"class":50,"line":212},[48,2074,2075],{"class":137},"now ",[48,2077,218],{"class":133},[48,2079,2080],{"class":137}," dt.datetime(",[48,2082,2083],{"class":167},"2026",[48,2085,485],{"class":137},[48,2087,1503],{"class":167},[48,2089,485],{"class":137},[48,2091,2092],{"class":167},"2",[48,2094,485],{"class":137},[48,2096,2097],{"class":167},"8",[48,2099,485],{"class":137},[48,2101,2102],{"class":167},"18",[48,2104,485],{"class":137},[48,2106,2107],{"class":260},"tzinfo",[48,2109,218],{"class":133},[48,2111,2112],{"class":137},"dt.timezone.utc)\n",[48,2114,2115,2118,2121,2123,2125,2127,2129,2132],{"class":50,"line":249},[48,2116,2117],{"class":133},"for",[48,2119,2120],{"class":137}," stamp ",[48,2122,471],{"class":133},[48,2124,474],{"class":137},[48,2126,566],{"class":58},[48,2128,485],{"class":137},[48,2130,2131],{"class":58},"\"2026-10-02T08:10:00Z\"",[48,2133,503],{"class":137},[48,2135,2136,2138,2141,2144,2147,2150,2152,2155],{"class":50,"line":303},[48,2137,1370],{"class":167},[48,2139,2140],{"class":137},"(stamp, ",[48,2142,2143],{"class":58},"\"→ run now\"",[48,2145,2146],{"class":133}," if",[48,2148,2149],{"class":137}," ready_to_run(stamp, now) ",[48,2151,328],{"class":133},[48,2153,2154],{"class":58}," \"→ wait, still being edited\"",[48,2156,191],{"class":137},[10,2158,2159,2160,2163,2164,31],{},"The poll or notification records that a change happened; the job runs only when ",[45,2161,2162],{},"lastModifiedDateTime"," is at least five minutes old. A save at 08:15 means \"still editing\" at 08:18; one at 08:10 is quiet enough to run. Pair this with a lock so two triggers cannot start the report at the same time, as in ",[27,2165,2167],{"href":2166},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fprevent-overlapping-runs-of-an-excel-report-job\u002F","Prevent Overlapping Runs of an Excel Report Job",[685,2169,694,2174,694,2177,694,2180,694,2183,694,2190,694,2194,694,2202,694,2208,694,2213,694,2217,694,2221,694,2225,694,2230,694,2235,694,2239,694,2241,694,2243,694,2246,694,2251,694,2254,694,2256,694,2258,694,2260,694,2263,694,2268,694,2273,694,2276,694,2278,694,2280,694,2283,694,2289,694,2295,694,2299,694,2302,694,2304],{"viewBox":2170,"role":688,"ariaLabelledBy":2171,"xmlns":692,"style":693},"4 0 752 313",[2172,2173],"od-debounce-t","od-debounce-d",[696,2175,2176],{"id":2172},"One editing session, one report run",[700,2178,2179],{"id":2173},"Saves at 08:02, 08:05, 08:09 and 08:10 each change the file. The job waits until five minutes have passed since the last save and runs the report once at 08:15.",[704,2181],{"x":706,"y":707,"width":708,"height":2182,"fill":710},"313",[50,2184],{"x1":2185,"y1":2186,"x2":2187,"y2":2186,"stroke":2188,"style":2189},"76.0","156.5","684.0","var(--muted,#5b6780)","stroke-width:2.5px",[704,2191],{"x":713,"y":714,"width":2192,"height":2193,"rx":1452,"fill":1464,"stroke":1465,"style":720},"136.0","108.5",[722,2195,2197],{"transform":2196},"translate(76.0,37.5) scale(1.000)",[726,2198],{"d":2199,"fill":729,"stroke":2200,"style":2201},"M6 3h9l4 4v14H6z M15 3v4h4","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[733,2203,2207],{"x":2204,"y":2205,"style":2206},"88.0","83.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","08:02 save",[733,2209,2212],{"x":2204,"y":2210,"style":2211},"101.5","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","change noted",[50,2214],{"x1":2204,"y1":2215,"x2":2204,"y2":2186,"stroke":2188,"style":2216},"124.5","stroke-width:1.5px",[2218,2219],"circle",{"cx":2204,"cy":2186,"r":2220,"fill":2188,"stroke":710,"style":720},"7.0",[704,2222],{"x":2223,"y":2224,"width":2192,"height":2193,"rx":1452,"fill":1464,"stroke":1465,"style":720},"166.0","188.5",[722,2226,2228],{"transform":2227},"translate(222.0,210.0) scale(1.000)",[726,2229],{"d":2199,"fill":729,"stroke":2200,"style":2201},[733,2231,2234],{"x":2232,"y":2233,"style":2206},"234.0","255.5","08:05 save",[733,2236,2238],{"x":2232,"y":2237,"style":2211},"274.0","still editing",[50,2240],{"x1":2232,"y1":2186,"x2":2232,"y2":2224,"stroke":2188,"style":2216},[2218,2242],{"cx":2232,"cy":2186,"r":2220,"fill":2188,"stroke":710,"style":720},[704,2244],{"x":2245,"y":714,"width":2192,"height":2193,"rx":1452,"fill":1464,"stroke":1465,"style":720},"312.0",[722,2247,2249],{"transform":2248},"translate(368.0,37.5) scale(1.000)",[726,2250],{"d":2199,"fill":729,"stroke":2200,"style":2201},[733,2252,2253],{"x":1468,"y":2205,"style":2206},"08:09 save",[733,2255,2238],{"x":1468,"y":2210,"style":2211},[50,2257],{"x1":1468,"y1":2215,"x2":1468,"y2":2186,"stroke":2188,"style":2216},[2218,2259],{"cx":1468,"cy":2186,"r":2220,"fill":2188,"stroke":710,"style":720},[704,2261],{"x":2262,"y":2224,"width":2192,"height":2193,"rx":1452,"fill":718,"stroke":719,"style":720},"458.0",[722,2264,2266],{"transform":2265},"translate(514.0,210.0) scale(1.000)",[726,2267],{"d":2199,"fill":729,"stroke":730,"style":2201},[733,2269,2272],{"x":2270,"y":2233,"style":2271},"526.0","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","08:10 save",[733,2274,2275],{"x":2270,"y":2237,"style":2211},"last change",[50,2277],{"x1":2270,"y1":2186,"x2":2270,"y2":2224,"stroke":2188,"style":2216},[2218,2279],{"cx":2270,"cy":2186,"r":2220,"fill":719,"stroke":710,"style":720},[704,2281],{"x":2282,"y":714,"width":2192,"height":2193,"rx":1452,"fill":780,"stroke":781,"style":720},"604.0",[722,2284,2286],{"transform":2285},"translate(660.0,29.0) scale(1.000)",[726,2287],{"d":2288,"fill":729,"stroke":788,"style":2201},"M13 2L4 14h7l-1 8 9-12h-7z",[733,2290,2294],{"x":2291,"y":2292,"style":2293},"672.0","74.5","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","08:15",[733,2296,2298],{"x":2291,"y":2297,"style":2211},"93.0","quiet 5 min → run",[733,2300,2301],{"x":2291,"y":1457,"style":2211},"once",[50,2303],{"x1":2291,"y1":2215,"x2":2291,"y2":2186,"stroke":2188,"style":2216},[2218,2305],{"cx":2291,"cy":2186,"r":2220,"fill":781,"stroke":710,"style":720},[33,2307,2309],{"id":2308},"step-5-choosing-between-the-three","Step 5 — Choosing between the three",[2311,2312,2313,2321,2327],"ul",{},[2314,2315,2316,2320],"li",{},[2317,2318,2319],"strong",{},"Polling the cTag"," needs no public endpoint and no subscription maintenance; a five-minute delay is usually acceptable. Best for one or a few files.",[2314,2322,2323,2326],{},[2317,2324,2325],{},"The delta query"," scales to folders and whole drives with one request per check, and is the right follow-up to any notification.",[2314,2328,2329,2332,2333,2335],{},[2317,2330,2331],{},"Webhooks"," react within seconds but need a public HTTPS endpoint, validation handling, ",[45,2334,1937],{}," checks and subscription renewal — worth it when minutes matter.",[10,2337,2338],{},"Many teams combine them: a webhook for speed, the delta query to find out what changed, and a daily delta-based sweep as a safety net in case a notification is lost.",[33,2340,2342],{"id":2341},"common-pitfalls","Common pitfalls",[2311,2344,2345,2354,2362,2368,2374],{},[2314,2346,2347,2350,2351,2353],{},[2317,2348,2349],{},"Watching the eTag."," Renames and sharing changes trigger reports; use the ",[45,2352,113],{}," for content.",[2314,2355,2356,2359,2360,31],{},[2317,2357,2358],{},"Running on every autosave."," Debounce on ",[45,2361,2162],{},[2314,2363,2364,2367],{},[2317,2365,2366],{},"Losing the delta link."," Without it, the next call re-enumerates everything; store it durably.",[2314,2369,2370,2373],{},[2317,2371,2372],{},"Slow webhook responses."," Answer validation and notifications within seconds; do the work in a background job.",[2314,2375,2376,2379],{},[2317,2377,2378],{},"Expired subscriptions."," Drive subscriptions expire within about a month; renew them on a schedule.",[33,2381,2383],{"id":2382},"conclusion","Conclusion",[10,2385,2386,2387,2389,2390,2392,2393,2395,2396,2398],{},"To run a report when a workbook in OneDrive or SharePoint changes, poll the file's ",[45,2388,113],{}," for a single file, use the Graph delta query and its ",[45,2391,831],{}," for folders, or subscribe to webhooks for immediate notifications — answering the ",[45,2394,1585],{}," handshake, checking ",[45,2397,1937],{},", and renewing subscriptions. Whichever trigger you use, debounce on the last-modified time so a burst of autosaves produces one report run.",[33,2400,2402],{"id":2401},"frequently-asked-questions","Frequently asked questions",[10,2404,2405,2408],{},[2317,2406,2407],{},"How do I detect when an Excel file in OneDrive has changed?","\nRead the drive item's cTag through Microsoft Graph and compare it with the value from your last check. The cTag changes only when the file content changes, unlike the eTag, which also changes on renames and sharing updates.",[10,2410,2411,2414],{},[2317,2412,2413],{},"What is the Microsoft Graph delta query?","\nAn endpoint on a drive or folder that returns all items on the first call and then, through the deltaLink it hands back, only the items that changed since the previous call. It is the efficient way to watch many files.",[10,2416,2417,2420],{},[2317,2418,2419],{},"How do Microsoft Graph webhooks validate my endpoint?","\nWhen you create a subscription, Graph sends a request with a validationToken query parameter, and your endpoint must return that token as plain text with status 200 within ten seconds.",[10,2422,2423,2426],{},[2317,2424,2425],{},"How do I avoid running a report many times while someone is still editing?","\nWait until the file's lastModifiedDateTime is a few minutes old before running, so a series of autosaves leads to a single run, and use a lock to stop two triggers running at once.",[33,2428,2430],{"id":2429},"related","Related",[2311,2432,2433,2439,2446,2453],{},[2314,2434,2435,2436,2438],{},"Up one level: ",[27,2437,30],{"href":29}," — working with cloud workbooks.",[2314,2440,2441,2445],{},[27,2442,2444],{"href":2443},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas\u002F","Read an Excel File from SharePoint or OneDrive into pandas"," — reading the changed file.",[2314,2447,2448,2452],{},[27,2449,2451],{"href":2450},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Fwatch-a-folder-and-process-new-excel-files\u002F","Watch a Folder and Process New Excel Files"," — the same idea for local folders.",[2314,2454,2455,2459],{},[27,2456,2458],{"href":2457},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fadd-a-chart-to-an-excel-online-workbook-with-microsoft-graph\u002F","Add a Chart to an Excel Online Workbook with Microsoft Graph"," — updating the workbook after the report runs.",[2461,2462,2463],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":43,"searchDepth":141,"depth":141,"links":2465},[2466,2467,2468,2469,2470,2471,2472,2473,2474,2475],{"id":35,"depth":141,"text":36},{"id":98,"depth":141,"text":99},{"id":824,"depth":141,"text":825},{"id":1578,"depth":141,"text":1579},{"id":1964,"depth":141,"text":1965},{"id":2308,"depth":141,"text":2309},{"id":2341,"depth":141,"text":2342},{"id":2382,"depth":141,"text":2383},{"id":2401,"depth":141,"text":2402},{"id":2429,"depth":141,"text":2430},"2026-10-02","Trigger a report as soon as someone saves a workbook in OneDrive or SharePoint, instead of on a fixed schedule: poll the file's cTag cheaply, use the Graph delta query to find every changed workbook in a folder, or receive webhook notifications and answer Microsoft's validation handshake — with debouncing so a burst of autosaves runs the report once, and the code tested against mocks.","md",[2480,2482,2484,2486],{"q":2407,"a":2481},"Read the drive item's cTag through Microsoft Graph and compare it with the value from your last check. The cTag changes only when the file content changes, unlike the eTag, which also changes on renames and sharing updates.",{"q":2413,"a":2483},"An endpoint on a drive or folder that returns all items on the first call and then, through the deltaLink it hands back, only the items that changed since the previous call. It is the efficient way to watch many files.",{"q":2419,"a":2485},"When you create a subscription, Graph sends a request with a validationToken query parameter, and your endpoint must return that token as plain text with status 200 within ten seconds.",{"q":2425,"a":2487},"Wait until the file's lastModifiedDateTime is a few minutes old before running, so a series of autosaves leads to a single run, and use a lock to stop two triggers running at once.","run a report when an excel file changes in onedrive",null,{"breadcrumb":2491},[2492,2495,2498],{"name":2493,"item":2494},"Home","\u002F",{"name":2496,"item":2497},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":30,"item":29},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive",{"title":2501,"description":2502},"Trigger a Python Report When a OneDrive Excel File Changes","React to Excel file changes in OneDrive\u002FSharePoint with Microsoft Graph: poll cTag vs eTag, delta queries with deltaLink, webhook subscriptions and the validationToken handshake, subscription renewal, debouncing autosaves, and tests with responses and Flask.","run-a-report-when-an-excel-file-changes-in-onedrive","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frun-a-report-when-an-excel-file-changes-in-onedrive\u002Findex",[2506,2507],"excel-online","scheduling","how-to","eFVk30yq1Je0LC8HqsqJXonRSzLTRUwaMIaAQsH_yAM",[2511,2515],{"title":2512,"path":2513,"stem":2514,"children":-1},"Recalculate Excel Online Workbooks with Graph Sessions","\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions\u002Findex",{"title":2516,"path":2517,"stem":2518,"children":-1},"Share an Excel File in OneDrive with a Link Using Graph","\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fshare-an-excel-file-in-onedrive-with-a-link-using-graph","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fshare-an-excel-file-in-onedrive-with-a-link-using-graph\u002Findex",[2520,2522],{"path":2521,"title":30},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph",{"path":2523,"title":2496},"\u002Fautomating-reporting-workflows",[],[2526,2529,2531,2534,2537,2539],{"path":2527,"title":2528,"kind":2508},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-with-microsoft-graph-api","Send an Excel Report with the Microsoft Graph API",{"path":2530,"title":2458,"kind":2508},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fadd-a-chart-to-an-excel-online-workbook-with-microsoft-graph",{"path":2532,"title":2533,"kind":2508},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fadd-rename-and-delete-worksheets-with-microsoft-graph","Add, Rename and Delete Worksheets with Microsoft Graph",{"path":2535,"title":2536,"kind":2508},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fappend-rows-to-an-excel-online-table-with-microsoft-graph","Append Rows to an Excel Online Table with Microsoft Graph",{"path":2538,"title":90,"kind":2508},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation",{"path":2540,"title":2541,"kind":2508},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",1790972119106]