[{"data":1,"prerenderedAt":2106},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph":3,"surround:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph":2077,"ancestors:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph":2085,"children:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph":2090,"related:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph":2091},{"id":4,"title":5,"body":6,"dateModified":2044,"datePublished":2044,"description":2045,"extension":2046,"faq":2047,"keyword":2056,"kind":2057,"meta":2058,"navigation":272,"path":2066,"seo":2067,"slug":2070,"stem":2071,"topics":2072,"type":2075,"__hash__":2076},"docs\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph\u002Findex.md","Call Excel Worksheet Functions Through Microsoft Graph",{"type":7,"value":8,"toc":2031},"minimark",[9,28,202,207,234,250,397,401,424,658,674,678,696,786,802,885,889,904,1084,1095,1099,1102,1335,1346,1350,1353,1560,1563,1567,1573,1758,1764,1900,1904,1946,1950,1968,1972,1978,1984,1990,1996,2000,2027],[10,11,12,13,17,18,21,22,27],"p",{},"Some calculations are defined by what Excel does. A loan schedule must match the ",[14,15,16],"code",{},"PMT"," figure the finance team sees in their workbook; a working-day count must follow ",[14,19,20],{},"NETWORKDAYS.INTL"," exactly; a lookup must use the table maintained in a SharePoint workbook. Reimplementing those in Python works until an edge case differs. If the workbook lives in OneDrive for Business or SharePoint, Microsoft Graph can evaluate Excel worksheet functions for you, in the cloud, with Excel's own engine. This guide, part of ",[23,24,26],"a",{"href":25},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002F","Automating Excel Online with Microsoft Graph",", shows the request shape, how to pass values and ranges, how to read results and errors, and how to batch many calls into one session.",[29,30,38,39,38,43,38,47,38,55,38,65,38,76,38,83,38,88,38,95,38,100,38,103,38,109,38,113,38,119,38,121,38,123,38,128,38,133,38,141,38,147,38,152,38,155,38,160,38,165,38,170,38,173,38,178,38,182,38,187,38,190,38,197],"svg",{"viewBox":31,"role":32,"ariaLabelledBy":33,"xmlns":36,"style":37},"4 0 752 322","img",[34,35],"gf-seq-t","gf-seq-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  ",[40,41,42],"title",{"id":34},"One worksheet function call through Graph",[44,45,46],"desc",{"id":35},"The Python script posts the function arguments to the workbook functions endpoint of a workbook in SharePoint. Graph hands the call to the Excel calculation service, which evaluates it against the workbook and returns a JSON body with the value and an error field.",[48,49],"rect",{"x":50,"y":51,"width":52,"height":53,"fill":54},"4","0","752","322","#ffffff",[48,56],{"x":57,"y":58,"width":59,"height":60,"rx":61,"fill":62,"stroke":63,"style":64},"20.0","16.0","180.0","78.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[66,67,69],"g",{"transform":68},"translate(98.0,30.5) scale(1.000)",[70,71],"path",{"d":72,"fill":73,"stroke":74,"style":75},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[77,78,82],"text",{"x":79,"y":80,"style":81},"110.0","76.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Python script",[48,84],{"x":85,"y":58,"width":59,"height":60,"rx":61,"fill":86,"stroke":87,"style":64},"290.0","#e7ebef","var(--line,#cdd5e6)",[66,89,91],{"transform":90},"translate(368.0,30.5) scale(1.000)",[70,92],{"d":93,"fill":73,"stroke":94,"style":75},"M7 18h10a4 4 0 0 0 .6-8A6 6 0 0 0 6.1 11.4 3.4 3.4 0 0 0 7 18z","var(--text,#172033)",[77,96,99],{"x":97,"y":80,"style":98},"380.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Microsoft Graph",[48,101],{"x":102,"y":58,"width":59,"height":60,"rx":61,"fill":86,"stroke":87,"style":64},"560.0",[66,104,106],{"transform":105},"translate(638.0,30.5) scale(1.000)",[70,107],{"d":108,"fill":73,"stroke":94,"style":75},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[77,110,112],{"x":111,"y":80,"style":98},"650.0","Excel service",[114,115],"line",{"x1":79,"y1":116,"x2":79,"y2":117,"stroke":87,"style":118},"94.0","306.0","stroke-width:1.5px;stroke-dasharray:5 4",[114,120],{"x1":97,"y1":116,"x2":97,"y2":117,"stroke":87,"style":118},[114,122],{"x1":111,"y1":116,"x2":111,"y2":117,"stroke":87,"style":118},[114,124],{"x1":125,"y1":126,"x2":127,"y2":126,"stroke":63,"style":64},"115.0","134.0","371.0",[129,130],"polygon",{"points":131,"fill":132},"375.0,134.0 366.0,139.0 366.0,129.0","#5b5cf0",[48,134],{"x":135,"y":136,"width":137,"height":138,"rx":139,"fill":54,"stroke":87,"style":140},"129.7","105.0","230.6","21.0","10","stroke-width:1.2px",[77,142,146],{"x":143,"y":144,"style":145},"245.0","119.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. POST …\u002Fworkbook\u002Ffunctions\u002Fpmt",[114,148],{"x1":149,"y1":150,"x2":151,"y2":150,"stroke":63,"style":64},"385.0","182.0","641.0",[129,153],{"points":154,"fill":132},"645.0,182.0 636.0,187.0 636.0,177.0",[48,156],{"x":157,"y":158,"width":159,"height":138,"rx":139,"fill":54,"stroke":87,"style":140},"414.0","153.0","201.9",[77,161,164],{"x":162,"y":163,"style":145},"515.0","167.5","2. evaluate PMT(rate, nper, pv)",[114,166],{"x1":167,"y1":168,"x2":169,"y2":168,"stroke":63,"style":64},"645.0","230.0","389.0",[129,171],{"points":172,"fill":132},"385.0,230.0 394.0,225.0 394.0,235.0",[48,174],{"x":175,"y":176,"width":177,"height":138,"rx":139,"fill":54,"stroke":87,"style":140},"454.0","201.0","122.0",[77,179,181],{"x":162,"y":180,"style":145},"215.5","3. result or #error",[114,183],{"x1":184,"y1":185,"x2":186,"y2":185,"stroke":63,"style":64},"375.0","278.0","119.0",[129,188],{"points":189,"fill":132},"115.0,278.0 124.0,273.0 124.0,283.0",[48,191],{"x":192,"y":193,"width":194,"height":138,"rx":139,"fill":195,"stroke":196,"style":140},"151.5","249.0","187.0","#d9f4f1","var(--teal,#0f9488)",[77,198,201],{"x":143,"y":199,"style":200},"263.5","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","4. {\"value\": …, \"error\": null}",[203,204,206],"h2",{"id":205},"prerequisites","Prerequisites",[208,209,214],"pre",{"className":210,"code":211,"language":212,"meta":213,"style":213},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install requests msal\n","bash","",[14,215,216],{"__ignoreMap":213},[217,218,220,224,228,231],"span",{"class":114,"line":219},1,[217,221,223],{"class":222},"sMTad","pip",[217,225,227],{"class":226},"srMev"," install",[217,229,230],{"class":226}," requests",[217,232,233],{"class":226}," msal\n",[10,235,236,237,240,241,244,245,249],{},"You need an access token with ",[14,238,239],{},"Files.ReadWrite"," (or ",[14,242,243],{},"Files.Read"," for pure lookups) and the drive and item IDs of a workbook in OneDrive for Business or SharePoint; getting both is covered in ",[23,246,248],{"href":247},"\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",". Personal OneDrive accounts do not support the workbook API. The examples read the token and IDs from the environment:",[208,251,255],{"className":252,"code":253,"language":254,"meta":213,"style":213},"language-python shiki shiki-themes github-light github-dark-high-contrast","import os\n\nimport requests\n\nGRAPH = \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\nBOOK = f\"{GRAPH}\u002Fdrives\u002F{os.environ['GRAPH_DRIVE_ID']}\u002Fitems\u002F{os.environ['GRAPH_ITEM_ID']}\u002Fworkbook\"\nhttp = requests.Session()\nhttp.headers[\"Authorization\"] = \"Bearer \" + os.environ[\"GRAPH_TOKEN\"]\n","python",[14,256,257,267,274,282,287,300,356,368],{"__ignoreMap":213},[217,258,259,263],{"class":114,"line":219},[217,260,262],{"class":261},"s-kum","import",[217,264,266],{"class":265},"skGVy"," os\n",[217,268,270],{"class":114,"line":269},2,[217,271,273],{"emptyLinePlaceholder":272},true,"\n",[217,275,277,279],{"class":114,"line":276},3,[217,278,262],{"class":261},[217,280,281],{"class":265}," requests\n",[217,283,285],{"class":114,"line":284},4,[217,286,273],{"emptyLinePlaceholder":272},[217,288,290,294,297],{"class":114,"line":289},5,[217,291,293],{"class":292},"sP0c6","GRAPH",[217,295,296],{"class":261}," =",[217,298,299],{"class":226}," \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\n",[217,301,303,306,308,311,314,318,320,323,326,328,331,334,337,339,342,344,346,349,351,353],{"class":114,"line":302},6,[217,304,305],{"class":292},"BOOK",[217,307,296],{"class":261},[217,309,310],{"class":261}," f",[217,312,313],{"class":226},"\"",[217,315,317],{"class":316},"sSjpA","{",[217,319,293],{"class":292},[217,321,322],{"class":316},"}",[217,324,325],{"class":226},"\u002Fdrives\u002F",[217,327,317],{"class":316},[217,329,330],{"class":265},"os.environ[",[217,332,333],{"class":226},"'GRAPH_DRIVE_ID'",[217,335,336],{"class":265},"]",[217,338,322],{"class":316},[217,340,341],{"class":226},"\u002Fitems\u002F",[217,343,317],{"class":316},[217,345,330],{"class":265},[217,347,348],{"class":226},"'GRAPH_ITEM_ID'",[217,350,336],{"class":265},[217,352,322],{"class":316},[217,354,355],{"class":226},"\u002Fworkbook\"\n",[217,357,359,362,365],{"class":114,"line":358},7,[217,360,361],{"class":265},"http ",[217,363,364],{"class":261},"=",[217,366,367],{"class":265}," requests.Session()\n",[217,369,371,374,377,380,382,385,388,391,394],{"class":114,"line":370},8,[217,372,373],{"class":265},"http.headers[",[217,375,376],{"class":226},"\"Authorization\"",[217,378,379],{"class":265},"] ",[217,381,364],{"class":261},[217,383,384],{"class":226}," \"Bearer \"",[217,386,387],{"class":261}," +",[217,389,390],{"class":265}," os.environ[",[217,392,393],{"class":226},"\"GRAPH_TOKEN\"",[217,395,396],{"class":265},"]\n",[203,398,400],{"id":399},"step-1-call-a-function-with-plain-values","Step 1 — Call a function with plain values",[10,402,403,404,407,408,411,412,415,416,419,420,423],{},"Each worksheet function has an endpoint under ",[14,405,406],{},"\u002Fworkbook\u002Ffunctions\u002F",", named after the function in lower camel case. The body is a JSON object whose keys are the function's argument names — ",[14,409,410],{},"PMT(rate, nper, pv)"," takes ",[14,413,414],{},"rate",", ",[14,417,418],{},"nper"," and ",[14,421,422],{},"pv",":",[208,425,427],{"className":252,"code":426,"language":254,"meta":213,"style":213},"def excel_function(name, **arguments):\n    response = http.post(f\"{BOOK}\u002Ffunctions\u002F{name}\", json=arguments, timeout=30)\n    response.raise_for_status()\n    body = response.json()\n    if body.get(\"error\"):\n        raise ValueError(f\"{name} returned {body['error']}\")\n    return body[\"value\"]\n\nmonthly = excel_function(\"pmt\", rate=0.05 \u002F 12, nper=36, pv=-25000)\nprint(round(monthly, 2))\n",[14,428,429,447,502,507,517,531,571,584,588,638],{"__ignoreMap":213},[217,430,431,434,438,441,444],{"class":114,"line":219},[217,432,433],{"class":261},"def",[217,435,437],{"class":436},"s_Opv"," excel_function",[217,439,440],{"class":265},"(name, ",[217,442,443],{"class":261},"**",[217,445,446],{"class":265},"arguments):\n",[217,448,449,452,454,457,460,462,464,466,468,471,473,476,478,480,482,486,488,491,494,496,499],{"class":114,"line":269},[217,450,451],{"class":265},"    response ",[217,453,364],{"class":261},[217,455,456],{"class":265}," http.post(",[217,458,459],{"class":261},"f",[217,461,313],{"class":226},[217,463,317],{"class":316},[217,465,305],{"class":292},[217,467,322],{"class":316},[217,469,470],{"class":226},"\u002Ffunctions\u002F",[217,472,317],{"class":316},[217,474,475],{"class":265},"name",[217,477,322],{"class":316},[217,479,313],{"class":226},[217,481,415],{"class":265},[217,483,485],{"class":484},"sa561","json",[217,487,364],{"class":261},[217,489,490],{"class":265},"arguments, ",[217,492,493],{"class":484},"timeout",[217,495,364],{"class":261},[217,497,498],{"class":292},"30",[217,500,501],{"class":265},")\n",[217,503,504],{"class":114,"line":276},[217,505,506],{"class":265},"    response.raise_for_status()\n",[217,508,509,512,514],{"class":114,"line":284},[217,510,511],{"class":265},"    body ",[217,513,364],{"class":261},[217,515,516],{"class":265}," response.json()\n",[217,518,519,522,525,528],{"class":114,"line":289},[217,520,521],{"class":261},"    if",[217,523,524],{"class":265}," body.get(",[217,526,527],{"class":226},"\"error\"",[217,529,530],{"class":265},"):\n",[217,532,533,536,539,542,544,546,548,550,552,555,557,560,563,565,567,569],{"class":114,"line":302},[217,534,535],{"class":261},"        raise",[217,537,538],{"class":292}," ValueError",[217,540,541],{"class":265},"(",[217,543,459],{"class":261},[217,545,313],{"class":226},[217,547,317],{"class":316},[217,549,475],{"class":265},[217,551,322],{"class":316},[217,553,554],{"class":226}," returned ",[217,556,317],{"class":316},[217,558,559],{"class":265},"body[",[217,561,562],{"class":226},"'error'",[217,564,336],{"class":265},[217,566,322],{"class":316},[217,568,313],{"class":226},[217,570,501],{"class":265},[217,572,573,576,579,582],{"class":114,"line":358},[217,574,575],{"class":261},"    return",[217,577,578],{"class":265}," body[",[217,580,581],{"class":226},"\"value\"",[217,583,396],{"class":265},[217,585,586],{"class":114,"line":370},[217,587,273],{"emptyLinePlaceholder":272},[217,589,591,594,596,599,602,604,606,608,611,614,617,619,621,623,626,628,630,633,636],{"class":114,"line":590},9,[217,592,593],{"class":265},"monthly ",[217,595,364],{"class":261},[217,597,598],{"class":265}," excel_function(",[217,600,601],{"class":226},"\"pmt\"",[217,603,415],{"class":265},[217,605,414],{"class":484},[217,607,364],{"class":261},[217,609,610],{"class":292},"0.05",[217,612,613],{"class":261}," \u002F",[217,615,616],{"class":292}," 12",[217,618,415],{"class":265},[217,620,418],{"class":484},[217,622,364],{"class":261},[217,624,625],{"class":292},"36",[217,627,415],{"class":265},[217,629,422],{"class":484},[217,631,632],{"class":261},"=-",[217,634,635],{"class":292},"25000",[217,637,501],{"class":265},[217,639,641,644,646,649,652,655],{"class":114,"line":640},10,[217,642,643],{"class":292},"print",[217,645,541],{"class":265},[217,647,648],{"class":292},"round",[217,650,651],{"class":265},"(monthly, ",[217,653,654],{"class":292},"2",[217,656,657],{"class":265},"))\n",[10,659,660,661,663,664,419,667,670,671,673],{},"The result is Excel's own ",[14,662,16],{},": the monthly repayment on a 25,000 loan over three years at 5% a year. The endpoint does not change the workbook; it evaluates the function in the workbook's context, which matters for functions that read ranges, as in the next step. Argument names follow Microsoft's documentation of each function — optional ones such as ",[14,665,666],{},"fv",[14,668,669],{},"type"," for ",[14,672,16],{}," can simply be left out.",[203,675,677],{"id":676},"step-2-pass-ranges-from-the-workbook","Step 2 — Pass ranges from the workbook",[10,679,680,681,415,684,687,688,691,692,695],{},"Functions such as ",[14,682,683],{},"VLOOKUP",[14,685,686],{},"SUMIFS"," or ",[14,689,690],{},"XIRR"," take ranges. Pass a range as an object with an ",[14,693,694],{},"Address",", and Excel reads the cells from the workbook:",[208,697,699],{"className":252,"code":698,"language":254,"meta":213,"style":213},"rate = excel_function(\n    \"vlookup\",\n    lookupValue=\"EUR\",\n    tableArray={\"Address\": \"Rates!A2:C20\"},\n    colIndexNum=2,\n    rangeLookup=False,\n)\nprint(rate)\n",[14,700,701,711,719,731,752,763,775,779],{"__ignoreMap":213},[217,702,703,706,708],{"class":114,"line":219},[217,704,705],{"class":265},"rate ",[217,707,364],{"class":261},[217,709,710],{"class":265}," excel_function(\n",[217,712,713,716],{"class":114,"line":269},[217,714,715],{"class":226},"    \"vlookup\"",[217,717,718],{"class":265},",\n",[217,720,721,724,726,729],{"class":114,"line":276},[217,722,723],{"class":484},"    lookupValue",[217,725,364],{"class":261},[217,727,728],{"class":226},"\"EUR\"",[217,730,718],{"class":265},[217,732,733,736,738,740,743,746,749],{"class":114,"line":284},[217,734,735],{"class":484},"    tableArray",[217,737,364],{"class":261},[217,739,317],{"class":265},[217,741,742],{"class":226},"\"Address\"",[217,744,745],{"class":265},": ",[217,747,748],{"class":226},"\"Rates!A2:C20\"",[217,750,751],{"class":265},"},\n",[217,753,754,757,759,761],{"class":114,"line":289},[217,755,756],{"class":484},"    colIndexNum",[217,758,364],{"class":261},[217,760,654],{"class":292},[217,762,718],{"class":265},[217,764,765,768,770,773],{"class":114,"line":302},[217,766,767],{"class":484},"    rangeLookup",[217,769,364],{"class":261},[217,771,772],{"class":292},"False",[217,774,718],{"class":265},[217,776,777],{"class":114,"line":358},[217,778,501],{"class":265},[217,780,781,783],{"class":114,"line":370},[217,782,643],{"class":292},[217,784,785],{"class":265},"(rate)\n",[10,787,788,789,792,793,796,797,801],{},"This is where Graph earns its place: the lookup uses the rate table as it is in SharePoint right now, maintained by whoever owns it, without downloading the workbook. Values and addresses can be mixed freely — a literal ",[14,790,791],{},"lookupValue"," with a range for ",[14,794,795],{},"tableArray",", as here. Reading ranges directly, rather than through a function, is covered in ",[23,798,800],{"href":799},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph\u002F","Read and Write Excel Online Ranges with Microsoft Graph",".",[29,803,38,808,38,811,38,814,38,817,38,822,38,826,38,831,38,835,38,840,38,842,38,847,38,850,38,854,38,856,38,860,38,863,38,867,38,869,38,873,38,876,38,879,38,881],{"viewBox":804,"role":32,"ariaLabelledBy":805,"xmlns":36,"style":37},"4 0 752 208",[806,807],"gf-args-t","gf-args-d",[40,809,810],{"id":806},"How arguments are written in the request body",[44,812,813],{"id":807},"A number or text argument is written as a plain JSON value. A range is an object with an Address such as Rates A2 to C20. An array of values is a JSON array. Optional arguments are simply left out. Argument names are the Excel argument names in lower camel case.",[48,815],{"x":50,"y":51,"width":52,"height":816,"fill":54},"208",[48,818],{"x":57,"y":58,"width":819,"height":820,"rx":51,"fill":54,"stroke":87,"style":821},"171.3","40.0","stroke-width:1px",[48,823],{"x":824,"y":58,"width":825,"height":820,"rx":51,"fill":62,"stroke":63,"style":140},"191.3","548.7",[77,827,830],{"x":828,"y":829,"style":81},"465.6","41.0","JSON in the body",[48,832],{"x":57,"y":833,"width":819,"height":834,"rx":51,"fill":86,"stroke":87,"style":821},"56.0","34.0",[77,836,839],{"x":837,"y":60,"style":838},"32.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","number or text",[48,841],{"x":824,"y":833,"width":825,"height":834,"rx":51,"fill":54,"stroke":87,"style":821},[77,843,846],{"x":828,"y":844,"style":845},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","\"rate\": 0.004167",[48,848],{"x":57,"y":849,"width":819,"height":834,"rx":51,"fill":86,"stroke":87,"style":821},"90.0",[77,851,853],{"x":837,"y":852,"style":838},"112.0","range in the workbook",[48,855],{"x":824,"y":849,"width":825,"height":834,"rx":51,"fill":54,"stroke":87,"style":821},[77,857,859],{"x":828,"y":858,"style":845},"111.0","\"tableArray\": {\"Address\": \"Rates!A2:C20\"}",[48,861],{"x":57,"y":862,"width":819,"height":834,"rx":51,"fill":86,"stroke":87,"style":821},"124.0",[77,864,866],{"x":837,"y":865,"style":838},"146.0","list of values",[48,868],{"x":824,"y":862,"width":825,"height":834,"rx":51,"fill":54,"stroke":87,"style":821},[77,870,872],{"x":828,"y":871,"style":845},"145.0","\"values\": [-1000, 300, 400]",[48,874],{"x":57,"y":875,"width":819,"height":834,"rx":51,"fill":86,"stroke":87,"style":821},"158.0",[77,877,878],{"x":837,"y":59,"style":838},"optional argument",[48,880],{"x":824,"y":875,"width":825,"height":834,"rx":51,"fill":54,"stroke":87,"style":821},[77,882,884],{"x":828,"y":883,"style":845},"179.0","leave it out",[203,886,888],{"id":887},"step-3-handle-excel-errors-as-results","Step 3 — Handle Excel errors as results",[10,890,891,892,895,896,899,900,903],{},"A function can succeed as an HTTP request and still return an Excel error — ",[14,893,894],{},"#N\u002FA"," for a lookup that finds nothing, ",[14,897,898],{},"#NUM!"," for an impossible calculation. Graph reports these in the ",[14,901,902],{},"error"," field with a successful status code. Decide per call whether an error is exceptional or an expected outcome:",[208,905,907],{"className":252,"code":906,"language":254,"meta":213,"style":213},"def excel_function_or_error(name, **arguments):\n    response = http.post(f\"{BOOK}\u002Ffunctions\u002F{name}\", json=arguments, timeout=30)\n    response.raise_for_status()\n    body = response.json()\n    return body.get(\"value\"), body.get(\"error\")\n\nvalue, error = excel_function_or_error(\n    \"vlookup\", lookupValue=\"XYZ\", tableArray={\"Address\": \"Rates!A2:C20\"}, colIndexNum=2, rangeLookup=False)\nif error == \"#N\u002FA\":\n    print(\"currency not in the rate table\")\n",[14,908,909,922,966,970,978,993,997,1007,1055,1072],{"__ignoreMap":213},[217,910,911,913,916,918,920],{"class":114,"line":219},[217,912,433],{"class":261},[217,914,915],{"class":436}," excel_function_or_error",[217,917,440],{"class":265},[217,919,443],{"class":261},[217,921,446],{"class":265},[217,923,924,926,928,930,932,934,936,938,940,942,944,946,948,950,952,954,956,958,960,962,964],{"class":114,"line":269},[217,925,451],{"class":265},[217,927,364],{"class":261},[217,929,456],{"class":265},[217,931,459],{"class":261},[217,933,313],{"class":226},[217,935,317],{"class":316},[217,937,305],{"class":292},[217,939,322],{"class":316},[217,941,470],{"class":226},[217,943,317],{"class":316},[217,945,475],{"class":265},[217,947,322],{"class":316},[217,949,313],{"class":226},[217,951,415],{"class":265},[217,953,485],{"class":484},[217,955,364],{"class":261},[217,957,490],{"class":265},[217,959,493],{"class":484},[217,961,364],{"class":261},[217,963,498],{"class":292},[217,965,501],{"class":265},[217,967,968],{"class":114,"line":276},[217,969,506],{"class":265},[217,971,972,974,976],{"class":114,"line":284},[217,973,511],{"class":265},[217,975,364],{"class":261},[217,977,516],{"class":265},[217,979,980,982,984,986,989,991],{"class":114,"line":289},[217,981,575],{"class":261},[217,983,524],{"class":265},[217,985,581],{"class":226},[217,987,988],{"class":265},"), body.get(",[217,990,527],{"class":226},[217,992,501],{"class":265},[217,994,995],{"class":114,"line":302},[217,996,273],{"emptyLinePlaceholder":272},[217,998,999,1002,1004],{"class":114,"line":358},[217,1000,1001],{"class":265},"value, error ",[217,1003,364],{"class":261},[217,1005,1006],{"class":265}," excel_function_or_error(\n",[217,1008,1009,1011,1013,1015,1017,1020,1022,1024,1026,1028,1030,1032,1034,1037,1040,1042,1044,1046,1049,1051,1053],{"class":114,"line":370},[217,1010,715],{"class":226},[217,1012,415],{"class":265},[217,1014,791],{"class":484},[217,1016,364],{"class":261},[217,1018,1019],{"class":226},"\"XYZ\"",[217,1021,415],{"class":265},[217,1023,795],{"class":484},[217,1025,364],{"class":261},[217,1027,317],{"class":265},[217,1029,742],{"class":226},[217,1031,745],{"class":265},[217,1033,748],{"class":226},[217,1035,1036],{"class":265},"}, ",[217,1038,1039],{"class":484},"colIndexNum",[217,1041,364],{"class":261},[217,1043,654],{"class":292},[217,1045,415],{"class":265},[217,1047,1048],{"class":484},"rangeLookup",[217,1050,364],{"class":261},[217,1052,772],{"class":292},[217,1054,501],{"class":265},[217,1056,1057,1060,1063,1066,1069],{"class":114,"line":590},[217,1058,1059],{"class":261},"if",[217,1061,1062],{"class":265}," error ",[217,1064,1065],{"class":261},"==",[217,1067,1068],{"class":226}," \"#N\u002FA\"",[217,1070,1071],{"class":265},":\n",[217,1073,1074,1077,1079,1082],{"class":114,"line":640},[217,1075,1076],{"class":292},"    print",[217,1078,541],{"class":265},[217,1080,1081],{"class":226},"\"currency not in the rate table\"",[217,1083,501],{"class":265},[10,1085,1086,1087,1090,1091,801],{},"HTTP-level failures — an expired token, a missing file, throttling — still raise through ",[14,1088,1089],{},"raise_for_status",". Keeping the two kinds apart means a missing currency becomes a line in a report, while a broken connection stops the job. Throttling in particular deserves its own handling; see ",[23,1092,1094],{"href":1093},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel\u002F","Handle Microsoft Graph Throttling in Excel Automation",[203,1096,1098],{"id":1097},"step-4-many-calls-in-one-session","Step 4 — Many calls in one session",[10,1100,1101],{},"Every call without a session makes the service open the workbook afresh. For a loop of hundreds of lookups, create a non-persistent session once and send its ID with each request; the service then keeps the workbook loaded between calls:",[208,1103,1105],{"className":252,"code":1104,"language":254,"meta":213,"style":213},"created = http.post(f\"{BOOK}\u002FcreateSession\", json={\"persistChanges\": False}, timeout=30)\ncreated.raise_for_status()\nhttp.headers[\"workbook-session-id\"] = created.json()[\"id\"]\ntry:\n    rates = {code: excel_function_or_error(\"vlookup\", lookupValue=code,\n                                           tableArray={\"Address\": \"Rates!A2:C20\"},\n                                           colIndexNum=2, rangeLookup=False)[0]\n             for code in [\"EUR\", \"GBP\", \"USD\"]}\nfinally:\n    http.post(f\"{BOOK}\u002FcloseSession\", timeout=30)\n    http.headers.pop(\"workbook-session-id\", None)\nprint(rates)\n",[14,1106,1107,1154,1159,1178,1185,1207,1224,1248,1277,1284,1312,1327],{"__ignoreMap":213},[217,1108,1109,1112,1114,1116,1118,1120,1122,1124,1126,1129,1131,1133,1135,1137,1140,1142,1144,1146,1148,1150,1152],{"class":114,"line":219},[217,1110,1111],{"class":265},"created ",[217,1113,364],{"class":261},[217,1115,456],{"class":265},[217,1117,459],{"class":261},[217,1119,313],{"class":226},[217,1121,317],{"class":316},[217,1123,305],{"class":292},[217,1125,322],{"class":316},[217,1127,1128],{"class":226},"\u002FcreateSession\"",[217,1130,415],{"class":265},[217,1132,485],{"class":484},[217,1134,364],{"class":261},[217,1136,317],{"class":265},[217,1138,1139],{"class":226},"\"persistChanges\"",[217,1141,745],{"class":265},[217,1143,772],{"class":292},[217,1145,1036],{"class":265},[217,1147,493],{"class":484},[217,1149,364],{"class":261},[217,1151,498],{"class":292},[217,1153,501],{"class":265},[217,1155,1156],{"class":114,"line":269},[217,1157,1158],{"class":265},"created.raise_for_status()\n",[217,1160,1161,1163,1166,1168,1170,1173,1176],{"class":114,"line":276},[217,1162,373],{"class":265},[217,1164,1165],{"class":226},"\"workbook-session-id\"",[217,1167,379],{"class":265},[217,1169,364],{"class":261},[217,1171,1172],{"class":265}," created.json()[",[217,1174,1175],{"class":226},"\"id\"",[217,1177,396],{"class":265},[217,1179,1180,1183],{"class":114,"line":284},[217,1181,1182],{"class":261},"try",[217,1184,1071],{"class":265},[217,1186,1187,1190,1192,1195,1198,1200,1202,1204],{"class":114,"line":289},[217,1188,1189],{"class":265},"    rates ",[217,1191,364],{"class":261},[217,1193,1194],{"class":265}," {code: excel_function_or_error(",[217,1196,1197],{"class":226},"\"vlookup\"",[217,1199,415],{"class":265},[217,1201,791],{"class":484},[217,1203,364],{"class":261},[217,1205,1206],{"class":265},"code,\n",[217,1208,1209,1212,1214,1216,1218,1220,1222],{"class":114,"line":302},[217,1210,1211],{"class":484},"                                           tableArray",[217,1213,364],{"class":261},[217,1215,317],{"class":265},[217,1217,742],{"class":226},[217,1219,745],{"class":265},[217,1221,748],{"class":226},[217,1223,751],{"class":265},[217,1225,1226,1229,1231,1233,1235,1237,1239,1241,1244,1246],{"class":114,"line":358},[217,1227,1228],{"class":484},"                                           colIndexNum",[217,1230,364],{"class":261},[217,1232,654],{"class":292},[217,1234,415],{"class":265},[217,1236,1048],{"class":484},[217,1238,364],{"class":261},[217,1240,772],{"class":292},[217,1242,1243],{"class":265},")[",[217,1245,51],{"class":292},[217,1247,396],{"class":265},[217,1249,1250,1253,1256,1259,1262,1264,1266,1269,1271,1274],{"class":114,"line":370},[217,1251,1252],{"class":261},"             for",[217,1254,1255],{"class":265}," code ",[217,1257,1258],{"class":261},"in",[217,1260,1261],{"class":265}," [",[217,1263,728],{"class":226},[217,1265,415],{"class":265},[217,1267,1268],{"class":226},"\"GBP\"",[217,1270,415],{"class":265},[217,1272,1273],{"class":226},"\"USD\"",[217,1275,1276],{"class":265},"]}\n",[217,1278,1279,1282],{"class":114,"line":590},[217,1280,1281],{"class":261},"finally",[217,1283,1071],{"class":265},[217,1285,1286,1289,1291,1293,1295,1297,1299,1302,1304,1306,1308,1310],{"class":114,"line":640},[217,1287,1288],{"class":265},"    http.post(",[217,1290,459],{"class":261},[217,1292,313],{"class":226},[217,1294,317],{"class":316},[217,1296,305],{"class":292},[217,1298,322],{"class":316},[217,1300,1301],{"class":226},"\u002FcloseSession\"",[217,1303,415],{"class":265},[217,1305,493],{"class":484},[217,1307,364],{"class":261},[217,1309,498],{"class":292},[217,1311,501],{"class":265},[217,1313,1315,1318,1320,1322,1325],{"class":114,"line":1314},11,[217,1316,1317],{"class":265},"    http.headers.pop(",[217,1319,1165],{"class":226},[217,1321,415],{"class":265},[217,1323,1324],{"class":292},"None",[217,1326,501],{"class":265},[217,1328,1330,1332],{"class":114,"line":1329},12,[217,1331,643],{"class":292},[217,1333,1334],{"class":265},"(rates)\n",[10,1336,1337,1340,1341,1345],{},[14,1338,1339],{},"persistChanges: False"," guarantees nothing is written back to the file, which suits read-only function calls. Close the session when done; sessions also expire on their own after a period of inactivity. Sessions and recalculation are covered in depth in ",[23,1342,1344],{"href":1343},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions\u002F","Recalculate Excel Online Workbooks with Graph Sessions",". For very large numbers of lookups, one call that reads the whole rate table and a Python dictionary lookup is faster still — use functions for what Python should not reimplement.",[203,1347,1349],{"id":1348},"when-a-function-is-not-exposed","When a function is not exposed",[10,1351,1352],{},"The functions endpoint covers several hundred worksheet functions, but not everything Excel offers; check Microsoft's list of workbook functions before relying on a particular one, especially functions added to Excel recently. The fallback is to let Excel evaluate a formula in a cell: open a non-persistent session, write the formula into an unused cell, read back its value, and close the session so nothing is saved:",[208,1354,1356],{"className":252,"code":1355,"language":254,"meta":213,"style":213},"created = http.post(f\"{BOOK}\u002FcreateSession\", json={\"persistChanges\": False}, timeout=30)\nhttp.headers[\"workbook-session-id\"] = created.json()[\"id\"]\ntry:\n    cell = f\"{BOOK}\u002Fworksheets('Scratch')\u002Frange(address='A1')\"\n    http.patch(cell, json={\"formulas\": [['=TEXTJOIN(\", \",TRUE,Rates!A2:A6)']]}, timeout=30).raise_for_status()\n    joined = http.get(cell, timeout=30).json()[\"values\"][0][0]\nfinally:\n    http.post(f\"{BOOK}\u002FcloseSession\", timeout=30)\n    http.headers.pop(\"workbook-session-id\", None)\nprint(joined)\n",[14,1357,1358,1402,1418,1424,1444,1476,1509,1515,1541,1553],{"__ignoreMap":213},[217,1359,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400],{"class":114,"line":219},[217,1361,1111],{"class":265},[217,1363,364],{"class":261},[217,1365,456],{"class":265},[217,1367,459],{"class":261},[217,1369,313],{"class":226},[217,1371,317],{"class":316},[217,1373,305],{"class":292},[217,1375,322],{"class":316},[217,1377,1128],{"class":226},[217,1379,415],{"class":265},[217,1381,485],{"class":484},[217,1383,364],{"class":261},[217,1385,317],{"class":265},[217,1387,1139],{"class":226},[217,1389,745],{"class":265},[217,1391,772],{"class":292},[217,1393,1036],{"class":265},[217,1395,493],{"class":484},[217,1397,364],{"class":261},[217,1399,498],{"class":292},[217,1401,501],{"class":265},[217,1403,1404,1406,1408,1410,1412,1414,1416],{"class":114,"line":269},[217,1405,373],{"class":265},[217,1407,1165],{"class":226},[217,1409,379],{"class":265},[217,1411,364],{"class":261},[217,1413,1172],{"class":265},[217,1415,1175],{"class":226},[217,1417,396],{"class":265},[217,1419,1420,1422],{"class":114,"line":276},[217,1421,1182],{"class":261},[217,1423,1071],{"class":265},[217,1425,1426,1429,1431,1433,1435,1437,1439,1441],{"class":114,"line":284},[217,1427,1428],{"class":265},"    cell ",[217,1430,364],{"class":261},[217,1432,310],{"class":261},[217,1434,313],{"class":226},[217,1436,317],{"class":316},[217,1438,305],{"class":292},[217,1440,322],{"class":316},[217,1442,1443],{"class":226},"\u002Fworksheets('Scratch')\u002Frange(address='A1')\"\n",[217,1445,1446,1449,1451,1453,1455,1458,1461,1464,1467,1469,1471,1473],{"class":114,"line":289},[217,1447,1448],{"class":265},"    http.patch(cell, ",[217,1450,485],{"class":484},[217,1452,364],{"class":261},[217,1454,317],{"class":265},[217,1456,1457],{"class":226},"\"formulas\"",[217,1459,1460],{"class":265},": [[",[217,1462,1463],{"class":226},"'=TEXTJOIN(\", \",TRUE,Rates!A2:A6)'",[217,1465,1466],{"class":265},"]]}, ",[217,1468,493],{"class":484},[217,1470,364],{"class":261},[217,1472,498],{"class":292},[217,1474,1475],{"class":265},").raise_for_status()\n",[217,1477,1478,1481,1483,1486,1488,1490,1492,1495,1498,1501,1503,1505,1507],{"class":114,"line":302},[217,1479,1480],{"class":265},"    joined ",[217,1482,364],{"class":261},[217,1484,1485],{"class":265}," http.get(cell, ",[217,1487,493],{"class":484},[217,1489,364],{"class":261},[217,1491,498],{"class":292},[217,1493,1494],{"class":265},").json()[",[217,1496,1497],{"class":226},"\"values\"",[217,1499,1500],{"class":265},"][",[217,1502,51],{"class":292},[217,1504,1500],{"class":265},[217,1506,51],{"class":292},[217,1508,396],{"class":265},[217,1510,1511,1513],{"class":114,"line":358},[217,1512,1281],{"class":261},[217,1514,1071],{"class":265},[217,1516,1517,1519,1521,1523,1525,1527,1529,1531,1533,1535,1537,1539],{"class":114,"line":370},[217,1518,1288],{"class":265},[217,1520,459],{"class":261},[217,1522,313],{"class":226},[217,1524,317],{"class":316},[217,1526,305],{"class":292},[217,1528,322],{"class":316},[217,1530,1301],{"class":226},[217,1532,415],{"class":265},[217,1534,493],{"class":484},[217,1536,364],{"class":261},[217,1538,498],{"class":292},[217,1540,501],{"class":265},[217,1542,1543,1545,1547,1549,1551],{"class":114,"line":590},[217,1544,1317],{"class":265},[217,1546,1165],{"class":226},[217,1548,415],{"class":265},[217,1550,1324],{"class":292},[217,1552,501],{"class":265},[217,1554,1555,1557],{"class":114,"line":640},[217,1556,643],{"class":292},[217,1558,1559],{"class":265},"(joined)\n",[10,1561,1562],{},"The scratch sheet must exist in the workbook — add a hidden one for the purpose — and because the session does not persist changes, the formula disappears when the session closes. This route evaluates anything Excel itself can, at the cost of two requests instead of one.",[203,1564,1566],{"id":1565},"step-5-check-the-result-once-in-python","Step 5 — Check the result once in Python",[10,1568,1569,1570,1572],{},"For well-defined functions, a local cross-check catches mistakes in argument names or signs before they reach a report. ",[14,1571,16],{}," has a closed form:",[208,1574,1576],{"className":252,"code":1575,"language":254,"meta":213,"style":213},"def pmt(rate, nper, pv, fv=0.0, when=0):\n    if rate == 0:\n        return -(pv + fv) \u002F nper\n    factor = (1 + rate) ** nper\n    return -(rate * (pv * factor + fv)) \u002F ((1 + rate * when) * (factor - 1))\n\nprint(round(pmt(0.05 \u002F 12, 36, -25000), 2))           # 749.27\n",[14,1577,1578,1602,1616,1639,1661,1715,1719],{"__ignoreMap":213},[217,1579,1580,1582,1585,1588,1590,1593,1596,1598,1600],{"class":114,"line":219},[217,1581,433],{"class":261},[217,1583,1584],{"class":436}," pmt",[217,1586,1587],{"class":265},"(rate, nper, pv, fv",[217,1589,364],{"class":261},[217,1591,1592],{"class":292},"0.0",[217,1594,1595],{"class":265},", when",[217,1597,364],{"class":261},[217,1599,51],{"class":292},[217,1601,530],{"class":265},[217,1603,1604,1606,1609,1611,1614],{"class":114,"line":269},[217,1605,521],{"class":261},[217,1607,1608],{"class":265}," rate ",[217,1610,1065],{"class":261},[217,1612,1613],{"class":292}," 0",[217,1615,1071],{"class":265},[217,1617,1618,1621,1624,1627,1630,1633,1636],{"class":114,"line":276},[217,1619,1620],{"class":261},"        return",[217,1622,1623],{"class":261}," -",[217,1625,1626],{"class":265},"(pv ",[217,1628,1629],{"class":261},"+",[217,1631,1632],{"class":265}," fv) ",[217,1634,1635],{"class":261},"\u002F",[217,1637,1638],{"class":265}," nper\n",[217,1640,1641,1644,1646,1649,1652,1654,1657,1659],{"class":114,"line":284},[217,1642,1643],{"class":265},"    factor ",[217,1645,364],{"class":261},[217,1647,1648],{"class":265}," (",[217,1650,1651],{"class":292},"1",[217,1653,387],{"class":261},[217,1655,1656],{"class":265}," rate) ",[217,1658,443],{"class":261},[217,1660,1638],{"class":265},[217,1662,1663,1665,1667,1670,1673,1676,1678,1681,1683,1686,1688,1691,1693,1695,1697,1699,1702,1704,1707,1710,1713],{"class":114,"line":289},[217,1664,575],{"class":261},[217,1666,1623],{"class":261},[217,1668,1669],{"class":265},"(rate ",[217,1671,1672],{"class":261},"*",[217,1674,1675],{"class":265}," (pv ",[217,1677,1672],{"class":261},[217,1679,1680],{"class":265}," factor ",[217,1682,1629],{"class":261},[217,1684,1685],{"class":265}," fv)) ",[217,1687,1635],{"class":261},[217,1689,1690],{"class":265}," ((",[217,1692,1651],{"class":292},[217,1694,387],{"class":261},[217,1696,1608],{"class":265},[217,1698,1672],{"class":261},[217,1700,1701],{"class":265}," when) ",[217,1703,1672],{"class":261},[217,1705,1706],{"class":265}," (factor ",[217,1708,1709],{"class":261},"-",[217,1711,1712],{"class":292}," 1",[217,1714,657],{"class":265},[217,1716,1717],{"class":114,"line":302},[217,1718,273],{"emptyLinePlaceholder":272},[217,1720,1721,1723,1725,1727,1730,1732,1734,1736,1738,1740,1742,1744,1746,1749,1751,1754],{"class":114,"line":358},[217,1722,643],{"class":292},[217,1724,541],{"class":265},[217,1726,648],{"class":292},[217,1728,1729],{"class":265},"(pmt(",[217,1731,610],{"class":292},[217,1733,613],{"class":261},[217,1735,616],{"class":292},[217,1737,415],{"class":265},[217,1739,625],{"class":292},[217,1741,415],{"class":265},[217,1743,1709],{"class":261},[217,1745,635],{"class":292},[217,1747,1748],{"class":265},"), ",[217,1750,654],{"class":292},[217,1752,1753],{"class":265},"))           ",[217,1755,1757],{"class":1756},"s-wDw","# 749.27\n",[10,1759,1760,1761,1763],{},"A spreadsheet's ",[14,1762,16],{}," for the same arguments — checked here in LibreOffice — returns 749.27 as well. The sign convention is Excel's: a loan received is a negative present value, so the payment comes out positive. When a local implementation and the Graph result agree for a handful of cases, the call is set up correctly; when they disagree, the argument names or signs are usually to blame.",[29,1765,38,1771,38,1774,38,1777,38,1782,38,1787,38,1793,38,1797,38,1801,38,1805,38,1808,38,1811,38,1816,38,1821,38,1826,38,1830,38,1834,38,1837,38,1840,38,1844,38,1847,38,1850,38,1853,38,1856,38,1859,38,1862,38,1865,38,1868,38,1872,38,1876,38,1879,38,1881,38,1884,38,1888,38,1891,38,1894,38,1897],{"viewBox":1766,"role":32,"ariaLabelledBy":1767,"xmlns":36,"style":1770},"3 0 753 238",[1768,1769],"gf-when-t","gf-when-d","width:100%;max-width:753px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[40,1772,1773],{"id":1768},"Excel engine through Graph or plain Python",[44,1775,1776],{"id":1769},"Use Graph functions when the result must match Excel exactly or depends on ranges maintained in a SharePoint workbook. Compute in Python when the logic is simple, the volume is high or the job must run without a network connection.",[48,1778],{"x":1779,"y":51,"width":1780,"height":1781,"fill":54},"3","753","238",[48,1783],{"x":1784,"y":58,"width":1785,"height":1786,"rx":61,"fill":62,"stroke":63,"style":64},"217.8","324.4","75.5",[66,1788,1790],{"transform":1789},"translate(368.0,29.0) scale(1.000)",[70,1791],{"d":1792,"fill":73,"stroke":74,"style":75},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5",[77,1794,1796],{"x":97,"y":1786,"style":1795},"font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","What does the calculation depend on?",[114,1798],{"x1":97,"y1":1799,"x2":97,"y2":1800,"stroke":63,"style":64},"91.5","111.5",[114,1802],{"x1":1803,"y1":1800,"x2":1804,"y2":1800,"stroke":63,"style":64},"104.0","656.0",[114,1806],{"x1":1803,"y1":1800,"x2":1803,"y2":1807,"stroke":63,"style":64},"157.5",[129,1809],{"points":1810,"fill":132},"104.0,161.5 99.0,152.5 109.0,152.5",[48,1812],{"x":1813,"y":1814,"width":1815,"height":138,"rx":139,"fill":54,"stroke":87,"style":140},"19.7","121.5","168.6",[77,1817,1820],{"x":1803,"y":1818,"style":1819},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","must match Excel exactly",[48,1822],{"x":57,"y":1823,"width":1824,"height":1825,"rx":61,"fill":62,"stroke":63,"style":64},"163.5","168.0","59.0",[77,1827,1829],{"x":1803,"y":1828,"style":1795},"189.0","Graph function",[77,1831,1833],{"x":1803,"y":1832,"style":845},"208.5","financial, date rules",[114,1835],{"x1":1836,"y1":1800,"x2":1836,"y2":1807,"stroke":63,"style":64},"288.0",[129,1838],{"points":1839,"fill":132},"288.0,161.5 283.0,152.5 293.0,152.5",[48,1841],{"x":1842,"y":1814,"width":1843,"height":138,"rx":139,"fill":54,"stroke":87,"style":140},"205.6","164.8",[77,1845,1846],{"x":1836,"y":1818,"style":1819},"shared workbook ranges",[48,1848],{"x":1849,"y":1823,"width":1824,"height":1825,"rx":61,"fill":195,"stroke":196,"style":64},"204.0",[77,1851,1829],{"x":1836,"y":1828,"style":1852},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[77,1854,1855],{"x":1836,"y":1832,"style":845},"Address arguments",[114,1857],{"x1":1858,"y1":1800,"x2":1858,"y2":1807,"stroke":63,"style":64},"472.0",[129,1860],{"points":1861,"fill":132},"472.0,161.5 467.0,152.5 477.0,152.5",[48,1863],{"x":1864,"y":1814,"width":1824,"height":138,"rx":139,"fill":54,"stroke":87,"style":140},"388.0",[77,1866,1867],{"x":1858,"y":1818,"style":1819},"simple logic, high volume",[48,1869],{"x":1864,"y":1823,"width":1824,"height":1825,"rx":61,"fill":1870,"stroke":1871,"style":64},"#fdefd8","var(--gold,#b4740a)",[77,1873,1875],{"x":1858,"y":1828,"style":1874},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Python",[77,1877,1878],{"x":1858,"y":1832,"style":845},"read the data once",[114,1880],{"x1":1804,"y1":1800,"x2":1804,"y2":1807,"stroke":63,"style":64},[129,1882],{"points":1883,"fill":132},"656.0,161.5 651.0,152.5 661.0,152.5",[48,1885],{"x":1886,"y":1814,"width":1887,"height":138,"rx":139,"fill":54,"stroke":87,"style":140},"617.3","77.4",[77,1889,1890],{"x":1804,"y":1818,"style":1819},"offline job",[48,1892],{"x":1893,"y":1823,"width":1824,"height":1825,"rx":61,"fill":86,"stroke":87,"style":64},"572.0",[77,1895,1875],{"x":1804,"y":1828,"style":1896},"font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[77,1898,1899],{"x":1804,"y":1832,"style":845},"no network calls",[203,1901,1903],{"id":1902},"common-pitfalls","Common pitfalls",[1905,1906,1907,1915,1928,1934,1940],"ul",{},[1908,1909,1910,1914],"li",{},[1911,1912,1913],"strong",{},"Personal OneDrive."," The workbook API works on OneDrive for Business and SharePoint only; personal accounts return errors.",[1908,1916,1917,1920,1921,1923,1924,1927],{},[1911,1918,1919],{},"Wrong argument names."," Names are the Excel argument names in lower camel case — ",[14,1922,791],{},", not ",[14,1925,1926],{},"lookup_value",". An unknown name is rejected with a 400 response that names the problem.",[1908,1929,1930,1933],{},[1911,1931,1932],{},"Tokens that expire mid-loop."," Access tokens last about an hour. For long runs, get a fresh token from MSAL's cache before each batch instead of once at start-up.",[1908,1935,1936,1939],{},[1911,1937,1938],{},"Assuming the session saved something."," A non-persistent session writes nothing back; results must be read from the responses, not from the file afterwards.",[1908,1941,1942,1945],{},[1911,1943,1944],{},"One call per row."," Hundreds of sequential function calls are slow and invite throttling. Use a session, and move bulk logic to Python where exactness does not require Excel.",[203,1947,1949],{"id":1948},"conclusion","Conclusion",[10,1951,1952,1953,1956,1957,1960,1961,1964,1965,1967],{},"Microsoft Graph lets Python use Excel's own calculation engine on workbooks in OneDrive for Business or SharePoint: POST the arguments to ",[14,1954,1955],{},"\u002Fworkbook\u002Ffunctions\u002F{name}"," with Excel's argument names in lower camel case, pass ranges as ",[14,1958,1959],{},"{\"Address\": …}",", read ",[14,1962,1963],{},"value"," and treat ",[14,1966,902],{}," as an Excel result rather than a failure. Wrap repeated calls in a non-persistent session, cross-check simple functions locally once, and keep high-volume logic in Python.",[203,1969,1971],{"id":1970},"frequently-asked-questions","Frequently asked questions",[10,1973,1974,1977],{},[1911,1975,1976],{},"Can I run an Excel function without having Excel installed?","\nYes, if the workbook is stored in OneDrive for Business or SharePoint. Microsoft Graph exposes Excel's calculation engine through POST \u002Fworkbook\u002Ffunctions\u002F{name}, which evaluates the function in the cloud and returns the result as JSON.",[10,1979,1980,1983],{},[1911,1981,1982],{},"How do I pass a cell range to a Graph function call?","\nPass an object with an Address property, such as {\"Address\": \"Rates!A2:C20\"}. Plain values and arrays of values are passed directly.",[10,1985,1986,1989],{},[1911,1987,1988],{},"What does the response look like?","\nA JSON object with a value field holding the result and an error field that is null on success, or holds the Excel error such as \"#N\u002FA\" when the function itself returns an error.",[10,1991,1992,1995],{},[1911,1993,1994],{},"Why use Graph instead of calculating in Python?","\nWhen a result must match exactly what Excel computes — financial functions, date arithmetic with Excel's conventions, or lookups against a maintained workbook — using Excel's engine avoids reimplementing its rules and edge cases.",[203,1997,1999],{"id":1998},"related","Related",[1905,2001,2002,2008,2013,2020],{},[1908,2003,2004,2005,2007],{},"Up one level: ",[23,2006,26],{"href":25}," — Excel files in OneDrive and SharePoint.",[1908,2009,2010,2012],{},[23,2011,1344],{"href":1343}," — sessions in depth.",[1908,2014,2015,2019],{},[23,2016,2018],{"href":2017},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas\u002F","Match Excel Rounding in pandas"," — reproducing an Excel rule locally instead.",[1908,2021,2022,2026],{},[23,2023,2025],{"href":2024},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fxlookup-equivalent-in-pandas\u002F","XLOOKUP Equivalent in pandas"," — lookups computed in Python.",[2028,2029,2030],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":213,"searchDepth":269,"depth":269,"links":2032},[2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043],{"id":205,"depth":269,"text":206},{"id":399,"depth":269,"text":400},{"id":676,"depth":269,"text":677},{"id":887,"depth":269,"text":888},{"id":1097,"depth":269,"text":1098},{"id":1348,"depth":269,"text":1349},{"id":1565,"depth":269,"text":1566},{"id":1902,"depth":269,"text":1903},{"id":1948,"depth":269,"text":1949},{"id":1970,"depth":269,"text":1971},{"id":1998,"depth":269,"text":1999},"2026-09-23","Use Excel's own calculation engine from Python without Excel installed — call worksheet functions such as PMT and VLOOKUP on a workbook in OneDrive or SharePoint through Microsoft Graph, pass values and ranges as arguments, read results and Excel errors, and reuse a session for many calls.","md",[2048,2050,2052,2054],{"q":1976,"a":2049},"Yes, if the workbook is stored in OneDrive for Business or SharePoint. Microsoft Graph exposes Excel's calculation engine through POST \u002Fworkbook\u002Ffunctions\u002F{name}, which evaluates the function in the cloud and returns the result as JSON.",{"q":1982,"a":2051},"Pass an object with an Address property, such as {\"Address\": \"Rates!A2:C20\"}. Plain values and arrays of values are passed directly.",{"q":1988,"a":2053},"A JSON object with a value field holding the result and an error field that is null on success, or holds the Excel error such as \"#N\u002FA\" when the function itself returns an error.",{"q":1994,"a":2055},"When a result must match exactly what Excel computes — financial functions, date arithmetic with Excel's conventions, or lookups against a maintained workbook — using Excel's engine avoids reimplementing its rules and edge cases.","call excel worksheet functions through microsoft graph",null,{"breadcrumb":2059},[2060,2062,2065],{"name":2061,"item":1635},"Home",{"name":2063,"item":2064},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":26,"item":25},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph",{"title":2068,"description":2069},"Call Excel Worksheet Functions Through Microsoft Graph (Python)","Evaluate Excel functions via the Microsoft Graph workbook functions endpoint — request shape, camelCase argument names, range arguments with Address, reading value and error, workbook sessions for repeated calls, and checking results against Python.","call-excel-worksheet-functions-through-microsoft-graph","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph\u002Findex",[2073,2074],"excel-online","excel-formulas","how-to","nqQwBv-lqJleoxNx_qcwKQ1vLNADhAnamAWh93Cx_s4",[2078,2081],{"title":248,"path":2079,"stem":2080,"children":-1},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation\u002Findex",{"title":2082,"path":2083,"stem":2084,"children":-1},"Copy a Template Workbook in SharePoint with Microsoft Graph","\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcopy-a-template-workbook-in-sharepoint-with-microsoft-graph","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcopy-a-template-workbook-in-sharepoint-with-microsoft-graph\u002Findex",[2086,2088],{"path":2087,"title":26},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph",{"path":2089,"title":2063},"\u002Fautomating-reporting-workflows",[],[2092,2095,2098,2101,2104,2105],{"path":2093,"title":2094,"kind":2075},"\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":2096,"title":2097,"kind":2075},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fadd-a-chart-to-an-excel-online-workbook-with-microsoft-graph","Add a Chart to an Excel Online Workbook with Microsoft Graph",{"path":2099,"title":2100,"kind":2075},"\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":2102,"title":2103,"kind":2075},"\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":2079,"title":248,"kind":2075},{"path":2083,"title":2082,"kind":2075},1790972118939]