Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Access the Microsoft Excel API (via Microsoft Graph) with managed OAuth authentication. Read and write workbooks, worksheets, ranges, tables, and charts stored in OneDrive or SharePoint.
.claude/skills/microsoft-excel/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 150% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 141% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 146% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 139% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 109% | 0% |
Access the Microsoft Excel API (via Microsoft Graph) with managed OAuth authentication. Read and write workbooks, worksheets, ranges, tables, and charts stored in OneDrive or SharePoint.
bash# List worksheets in a workbook python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF
https://gateway.maton.ai/microsoft-excel/{native-api-path}Replace {native-api-path} with the actual Microsoft Graph API endpoint path. The gateway proxies requests to graph.microsoft.com and automatically injects your OAuth token.
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEYEnvironment Variable: Set your API key as MATON_API_KEY:
bashexport MATON_API_KEY="YOUR_API_KEY"
Manage your Microsoft Excel OAuth connections at https://ctrl.maton.ai.
bashpython <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections?app=microsoft-excel&status=ACTIVE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF
bashpython <<'EOF' import urllib.request, os, json data = json.dumps({'app': 'microsoft-excel'}).encode() req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Content-Type', 'application/json') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF
bashpython <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF
Response:
json{ "connection": { "connection_id": "4751ac89-3970-47e1-872c-eacdf4291732", "status": "ACTIVE", "creation_time": "2026-02-07T00:43:18.565932Z", "last_updated_time": "2026-02-07T00:43:29.729782Z", "url": "https://connect.maton.ai/?session_token=...", "app": "microsoft-excel", "metadata": {} } }
Open the returned url in a browser to complete OAuth authorization.
bashpython <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF
If you have multiple Microsoft Excel connections, specify which one to use with the Maton-Connection header:
bashpython <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/microsoft-excel/v1.0/me/drive') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Maton-Connection', '4751ac89-3970-47e1-872c-eacdf4291732') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF
If omitted, the gateway uses the default (oldest) active connection.
You can access workbooks using either ID-based or path-based patterns:
By File ID:
/microsoft-excel/v1.0/me/drive/items/{file-id}/workbook/...By File Path:
/microsoft-excel/v1.0/me/drive/root:/{path-to-file}:/workbook/...bashGET /microsoft-excel/v1.0/me/drive
bashGET /microsoft-excel/v1.0/me/drive/root/children
bashGET /microsoft-excel/v1.0/me/drive/root/search(q='.xlsx')
bashPUT /microsoft-excel/v1.0/me/drive/root:/{filename}.xlsx:/content Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet {binary xlsx content}
Sessions improve performance for multiple operations. Recommended for batch operations.
bashPOST /microsoft-excel/v1.0/me/drive/root:/{path}:/workbook/createSession Content-Type: application/json { "persistChanges": true }
Response:
json{ "persistChanges": true, "id": "cluster=PUS7&session=..." }
Use the session ID in subsequent requests:
workbook-session-id: {session-id}bashPOST /microsoft-excel/v1.0/me/drive/root:/{path}:/workbook/closeSession workbook-session-id: {session-id}
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets
Response:
json{ "value": [ { "id": "{00000000-0001-0000-0000-000000000000}", "name": "Sheet1", "position": 0, "visibility": "Visible" } ] }
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')
bashPOST /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets Content-Type: application/json { "name": "NewSheet" }
bashPATCH /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1') Content-Type: application/json { "name": "RenamedSheet", "position": 2 }
bashDELETE /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('{worksheet-id}')
Returns 204 No Content on success.
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/range(address='A1:B2')
Response:
json{ "address": "Sheet1!A1:B2", "values": [ ["Hello", "World"], [1, 2] ], "formulas": [ ["Hello", "World"], [1, 2] ], "text": [ ["Hello", "World"], ["1", "2"] ], "numberFormat": [ ["General", "General"], ["General", "General"] ], "rowCount": 2, "columnCount": 2 }
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/usedRange
bashPATCH /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/range(address='A1:B2') Content-Type: application/json { "values": [ ["Updated", "Values"], [100, 200] ] }
bashPOST /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/range(address='A1:B2')/clear Content-Type: application/json { "applyTo": "All" }
Options: All, Formats, Contents
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/tables
bashPOST /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/tables/add Content-Type: application/json { "address": "A1:C4", "hasHeaders": true }
Response:
json{ "id": "{6D182180-5F5F-448B-9E9C-377A5251CFC5}", "name": "Table1", "showHeaders": true, "showTotals": false, "style": "TableStyleMedium2" }
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/tables('Table1')
bashPATCH /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/tables('Table1') Content-Type: application/json { "name": "PeopleTable", "showTotals": true }
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/tables('Table1')/rows
Response:
json{ "value": [ { "index": 0, "values": [["Alice", 30, "NYC"]] }, { "index": 1, "values": [["Bob", 25, "LA"]] } ] }
bashPOST /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/tables('Table1')/rows Content-Type: application/json { "values": [["Carol", 35, "Chicago"]] }
bashDELETE /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/tables('Table1')/rows/itemAt(index=0)
Returns 204 No Content on success.
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/tables('Table1')/columns
bashPOST /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/tables('Table1')/columns Content-Type: application/json { "values": [["Email"], ["alice@example.com"], ["bob@example.com"]] }
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/names
bashGET /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/charts
bashPOST /microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets('Sheet1')/charts/add Content-Type: application/json { "type": "ColumnClustered", "sourceData": "A1:C4", "seriesBy": "Auto" }
javascript// Get range values const response = await fetch( "https://gateway.maton.ai/microsoft-excel/v1.0/me/drive/root:/data.xlsx:/workbook/worksheets('Sheet1')/range(address='A1:B10')", { headers: { 'Authorization': `Bearer ${process.env.MATON_API_KEY}` } } ); const data = await response.json(); console.log(data.values);
pythonimport os import requests # Update range values response = requests.patch( "https://gateway.maton.ai/microsoft-excel/v1.0/me/drive/root:/data.xlsx:/workbook/worksheets('Sheet1')/range(address='A1:B2')", headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}, json={'values': [['Name', 'Age'], ['Alice', 30]]} ) print(response.json())
.xlsx files are supported (not legacy .xls){ and } must be URL-encoded (%7B and %7D)null in value arrays to skip updating specific cells"" (empty string)A1:C10, Sheet1!A1:B5)curl -g when URLs contain parentheses to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments| Status | Meaning | |--------|---------| | 400 | Missing Microsoft Excel connection or invalid request | | 401 | Invalid or missing Maton API key | | 404 | Item not found or session expired | | 429 | Rate limited | | 4xx/5xx | Passthrough error from Microsoft Graph API |
| Code | Description | |------|-------------| | ItemNotFound | File or resource doesn't exist | | ItemAlreadyExists | Worksheet or table with that name already exists | | InvalidArgument | Invalid parameter or missing required field | | SessionNotFound | Session expired or doesn't exist |
MATON_API_KEY environment variable is set:bashecho $MATON_API_KEY
bashpython <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF
microsoft-excel. For example:https://gateway.maton.ai/microsoft-excel/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheetshttps://gateway.maton.ai/v1.0/me/drive/root:/workbook.xlsx:/workbook/worksheets| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 8,842 | 7,579 | -14% | 1 | 1 | 0% | 2,467 | 6,159 | +150% | 0 | 0 | — |
case-02 | fail→pass | 12,257 | 12,752 | +4% | 1 | 1 | 0% | 2,725 | 6,559 | +141% | 0 | 0 | — |
case-03 | fail→pass | 10,387 | 5,634 | -46% | 1 | 1 | 0% | 2,315 | 5,699 | +146% | 0 | 0 | — |
case-04 | fail→pass | 9,624 | 5,035 | -48% | 1 | 1 | 0% | 2,118 | 5,067 | +139% | 0 | 0 | — |
case-05 | fail→pass | 11,885 | 4,349 | -63% | 1 | 1 | 0% | 2,502 | 5,227 | +109% | 0 | 0 | — |
case-06 | fail→pass | 7,714 | 3,857 | -50% | 1 | 1 | 0% | 1,616 | 4,932 | +205% | 0 | 0 | — |
case-07 | fail→pass | 7,075 | 4,069 | -42% | 1 | 1 | 0% | 1,457 | 5,167 | +255% | 0 | 0 | — |
case-08 | fail→pass | 8,974 | 4,305 | -52% | 1 | 1 | 0% | 1,903 | 5,241 | +175% | 0 | 0 | — |
case-09 | fail→fail | 7,516 | 3,817 | -49% | 1 | 1 | 0% | 1,422 | 5,127 | +261% | 0 | 0 | — |
case-10 | fail→fail | 8,510 | 9,663 | +14% | 1 | 1 | 0% | 1,647 | 5,350 | +225% | 0 | 0 | — |
case-11 | fail→pass | 6,213 | 4,169 | -33% | 1 | 1 | 0% | 1,271 | 5,213 | +310% | 0 | 0 | — |
case-12 | fail→pass | 9,018 | 5,450 | -40% | 1 | 1 | 0% | 1,910 | 5,708 | +199% | 0 | 0 | — |
case-13 | fail→pass | 6,017 | 3,206 | -47% | 1 | 1 | 0% | 1,162 | 4,907 | +322% | 0 | 0 | — |
case-14 | fail→fail | 3,633 | 2,663 | -27% | 1 | 1 | 0% | 702 | 4,831 | +588% | 0 | 0 | — |
case-15 | pass→pass | 6,641 | 3,003 | -55% | 1 | 1 | 0% | 1,326 | 4,980 | +276% | 0 | 0 | — |
case-16 | fail→pass | 8,346 | 4,723 | -43% | 1 | 1 | 0% | 1,732 | 5,288 | +205% | 0 | 0 | — |
case-17 | fail→fail | 14,275 | 2,589 | -82% | 1 | 1 | 0% | 2,452 | 4,722 | +93% | 0 | 0 | — |
case-18 | fail→pass | 8,701 | 6,974 | -20% | 1 | 1 | 0% | 1,818 | 5,227 | +188% | 0 | 0 | — |
case-19 | fail→fail | 6,717 | 3,695 | -45% | 1 | 1 | 0% | 1,306 | 5,122 | +292% | 0 | 0 | — |
case-20 | fail→fail | 4,613 | 4,075 | -12% | 1 | 1 | 0% | 954 | 5,207 | +446% | 0 | 0 | — |
case-21 | fail→fail | 4,505 | 3,678 | -18% | 1 | 1 | 0% | 886 | 4,977 | +462% | 0 | 0 | — |
case-22 | fail→fail | 7,579 | 8,471 | +12% | 1 | 1 | 0% | 1,661 | 6,242 | +276% | 0 | 0 | — |
case-23 | fail→fail | 5,913 | 6,791 | +15% | 1 | 1 | 0% | 1,201 | 5,834 | +386% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +57 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.