Migrating SharePoint Online REST API storage operations to the Graph API William Ott Published: February 22, 2019 Table of ContentsIntroductionRetrieving information about SPO & ODB items using SPO identifiersUsing v2.0 of the SPO REST APIThoughts on the Graph and SPO REST APIsHow Kloudless helpsIntroductionThis is a short guide on how to migrate from the older SharePoint Online REST API, which includes OneDrive for Business, to the new Graph API. It covers operations related to storage, such as files and folders. Migrating isn’t an easy task due to partial or missing documentation for both APIs and sometimes involves trail and error to figure out what works and what doesn’t.We’ll first take a look how to retrieve SharePoint Online (SPO) and OneDrive for Business (ODB) file and folder metadata from the Graph API using SPO REST API identifiers.We’ll then show how to achieve the same using the SPO REST API v2.0, that seems, at first glance, identical to the Graph API, but comes with it’s own hurdles.Finally, we’ll discuss how the Kloudless API can help with integrating these Office 365 services.Code includedThis blog post includes code samples but doesn’t cover how to authenticate a client to use for the API requests. Please refer to Microsoft’s documentation for more information on how to accomplish this.All the examples below use curl to perform requests to the Office 365 APIs.Retrieving information about SPO & ODB items using SPO identifiersThe following Graph API URLs provide the most straightforward way to retrieve any list item’s metadata:/sites/{site-id}/items/{item-id}/sites/{domain}:/{site-path}:/items/{item-id}The item-id in this case must be a ListItemUniqueId. Please refer to the Graph API docs for the specifics of what constitutes a valid site-id.In the code below, we attempt to retrieve sufficient information from the SPO API to identify the item via the Graph API instead.First, we make a request using the SPO API to retrieve an item’s metadata along with it’s ListId and UniqueId. We use the item’s site path in this first request. curl -s -H "$AUTH" 'https://example.sharepoint.com/sites/new-sc/_api/web/getfilebyserverrelativepath(decodedurl=%27/sites/new-sc/shared documents/test.docx%27)?$expand=ListItemAllFields&$select=ListId,UniqueId'1curl -s -H "$AUTH" 'https://example.sharepoint.com/sites/new-sc/_api/web/getfilebyserverrelativepath(decodedurl=%27/sites/new-sc/shared documents/test.docx%27)?$expand=ListItemAllFields&$select=ListId,UniqueId' { "ListId": "5aa9863e-8c76-4732-8c3f-bbf91f96027f", "ListItemAllFields": { ... (other attributes omitted for brevity) ... }, "UniqueId": "91968a70-005c-48e1-87c5-00d84b9d3b4f", "odata.editLink": "Web/GetFileByServerRelativePath(decodedurl='/sites/new-sc/shared%20documents/test.docx')", "odata.id": "https://example.sharepoint.com/sites/new-sc/_api/Web/GetFileByServerRelativePath(decodedurl='/sites/new-sc/shared documents/test.docx')", ... (other attributes omitted for brevity) ... "odata.type": "SP.File" }1234567891011{ "ListId": "5aa9863e-8c76-4732-8c3f-bbf91f96027f", "ListItemAllFields": { ... (other attributes omitted for brevity) ... }, "UniqueId": "91968a70-005c-48e1-87c5-00d84b9d3b4f", "odata.editLink": "Web/GetFileByServerRelativePath(decodedurl='/sites/new-sc/shared%20documents/test.docx')", "odata.id": "https://example.sharepoint.com/sites/new-sc/_api/Web/GetFileByServerRelativePath(decodedurl='/sites/new-sc/shared documents/test.docx')", ... (other attributes omitted for brevity) ... "odata.type": "SP.File"}The JSON data returned above provides the required Item ID in the UniqueId attribute, which we can use with the Graph API to retrieve the item’s data: curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/items/91968a70-005c-48e1-87c5-00d84b9d3b4f'1curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/items/91968a70-005c-48e1-87c5-00d84b9d3b4f' {"@odata.type": "#microsoft.graph.listItem", "contentType": {"id": "0x0101001F6376DD443C4044A80676ABDBDC6CB3"}, "createdBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "createdDateTime": "2019-02-13T01:40:52Z", "fields": {... (omitted for brevity) ...}, "id": "5", "lastModifiedBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "lastModifiedDateTime": "2019-02-13T01:41:27Z", "parentReference": {"id": "e0643a79-f57f-413c-a659-181451640d9b"}, "webUrl": "https://example.sharepoint.com/sites/new-sc/Shared%20Documents/test.docx"}1234567891011121314{"@odata.type": "#microsoft.graph.listItem", "contentType": {"id": "0x0101001F6376DD443C4044A80676ABDBDC6CB3"}, "createdBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "createdDateTime": "2019-02-13T01:40:52Z", "fields": {... (omitted for brevity) ...}, "id": "5", "lastModifiedBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "lastModifiedDateTime": "2019-02-13T01:41:27Z", "parentReference": {"id": "e0643a79-f57f-413c-a659-181451640d9b"}, "webUrl": "https://example.sharepoint.com/sites/new-sc/Shared%20Documents/test.docx"}However, the response only returns the baseItem, and not the actual Drive Item we want to obtain. It also returns the ListItemId as the id and not the unique GUID. In order to actually obtain the SharePoint IDs and the driveItem, we need to use the following URL for the Graph API instead, which require the ListId:/sites/{site-id}/lists/{list-id}/items/{item-id}/sites/{domain}:/{site-path}:/lists/{list-id}/items/{item-id}The initial response from the SPO REST API contains the list-id to use in the ListId attribute. curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/lists/5aa9863e-8c76-4732-8c3f-bbf91f96027f/items/91968a70-005c-48e1-87c5-00d84b9d3b4f?$expand=driveItem($select=sharepointIds),fields'1curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/lists/5aa9863e-8c76-4732-8c3f-bbf91f96027f/items/91968a70-005c-48e1-87c5-00d84b9d3b4f?$expand=driveItem($select=sharepointIds),fields' {"contentType": {"id": "0x0101001F6376DD443C4044A80676ABDBDC6CB3", "name": "Document"}, "createdBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "createdDateTime": "2019-02-13T01:40:52Z", "driveItem": {"sharepointIds": {"listId": "5aa9863e-8c76-4732-8c3f-bbf91f96027f", "listItemId": "5", "listItemUniqueId": "91968a70-005c-48e1-87c5-00d84b9d3b4f", "siteId": "6f577f5b-d764-41bb-87eb-0e759bf7adad", "siteUrl": "https://example.sharepoint.com/sites/new-sc", "tenantId": "924c1510-8d4b-4120-ae02-145453bc935b", "webId": "e3690907-0af7-4fab-8d5b-01f7966f180b"}}, "fields": {... (omitted for brevity) ...}, "id": "5", "lastModifiedBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "lastModifiedDateTime": "2019-02-13T01:41:27Z", "parentReference": {"id": "e0643a79-f57f-413c-a659-181451640d9b", "siteId": "example.sharepoint.com,6f577f5b-d764-41bb-87eb-0e759bf7adad,e3690907-0af7-4fab-8d5b-01f7966f180b"}, "webUrl": "https://example.sharepoint.com/sites/new-sc/Shared%20Documents/test.docx"}12345678910111213141516171819202122{"contentType": {"id": "0x0101001F6376DD443C4044A80676ABDBDC6CB3", "name": "Document"}, "createdBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "createdDateTime": "2019-02-13T01:40:52Z", "driveItem": {"sharepointIds": {"listId": "5aa9863e-8c76-4732-8c3f-bbf91f96027f", "listItemId": "5", "listItemUniqueId": "91968a70-005c-48e1-87c5-00d84b9d3b4f", "siteId": "6f577f5b-d764-41bb-87eb-0e759bf7adad", "siteUrl": "https://example.sharepoint.com/sites/new-sc", "tenantId": "924c1510-8d4b-4120-ae02-145453bc935b", "webId": "e3690907-0af7-4fab-8d5b-01f7966f180b"}}, "fields": {... (omitted for brevity) ...}, "id": "5", "lastModifiedBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "lastModifiedDateTime": "2019-02-13T01:41:27Z", "parentReference": {"id": "e0643a79-f57f-413c-a659-181451640d9b", "siteId": "example.sharepoint.com,6f577f5b-d764-41bb-87eb-0e759bf7adad,e3690907-0af7-4fab-8d5b-01f7966f180b"}, "webUrl": "https://example.sharepoint.com/sites/new-sc/Shared%20Documents/test.docx"}We select the sharepointIds attribute of the driveItem explicitly to obtain it in the response. To obtain additional listItem fields that contain metadata on the SPO ListItem object, expand the fields property as shown below: curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/lists/5aa9863e-8c76-4732-8c3f-bbf91f96027f/items/91968a70-005c-48e1-87c5-00d84b9d3b4f?$expand=driveItem,fields'1curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/lists/5aa9863e-8c76-4732-8c3f-bbf91f96027f/items/91968a70-005c-48e1-87c5-00d84b9d3b4f?$expand=driveItem,fields' {"contentType": {"id": "0x0101001F6376DD443C4044A80676ABDBDC6CB3", "name": "Document"}, "createdBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "createdDateTime": "2019-02-13T01:40:52Z", "driveItem": {... (omitted for brevity) ..., "cTag": ""c:{91968A70-005C-48E1-87C5-00D84B9D3B4F},6"", "id": "016YYZOETQRKLJCXAA4FEIPRIA3BFZ2O2P", "parentReference": {"driveId": "b!W39Xb2TXu0GH6w51m_etrQcJaeP3CqtPjVsB95ZvGAs-hqladowyR4w_u_kflgJ_", "driveType": "documentLibrary", "id": "016YYZOEV6Y2GOVW7725BZO354PWSELRRZ", "path": "/drives/b!W39Xb2TXu0GH6w51m_etrQcJaeP3CqtPjVsB95ZvGAs-hqladowyR4w_u_kflgJ_/root:"}, }, "fields": {... (omitted for brevity) ...}, "id": "5", "lastModifiedBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "lastModifiedDateTime": "2019-02-13T01:41:27Z", "parentReference": {"id": "e0643a79-f57f-413c-a659-181451640d9b", "siteId": "example.sharepoint.com,6f577f5b-d764-41bb-87eb-0e759bf7adad,e3690907-0af7-4fab-8d5b-01f7966f180b"}, "webUrl": "https://example.sharepoint.com/sites/new-sc/Shared%20Documents/test.docx"}1234567891011121314151617181920212223{"contentType": {"id": "0x0101001F6376DD443C4044A80676ABDBDC6CB3", "name": "Document"}, "createdBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "createdDateTime": "2019-02-13T01:40:52Z", "driveItem": {... (omitted for brevity) ..., "cTag": ""c:{91968A70-005C-48E1-87C5-00D84B9D3B4F},6"", "id": "016YYZOETQRKLJCXAA4FEIPRIA3BFZ2O2P", "parentReference": {"driveId": "b!W39Xb2TXu0GH6w51m_etrQcJaeP3CqtPjVsB95ZvGAs-hqladowyR4w_u_kflgJ_", "driveType": "documentLibrary", "id": "016YYZOEV6Y2GOVW7725BZO354PWSELRRZ", "path": "/drives/b!W39Xb2TXu0GH6w51m_etrQcJaeP3CqtPjVsB95ZvGAs-hqladowyR4w_u_kflgJ_/root:"}, }, "fields": {... (omitted for brevity) ...}, "id": "5", "lastModifiedBy": {"user": {"displayName": "kloudless admin", "email": "kloudless@example.kloudless.com", "id": "21509bcb-d48c-4d66-8222-9da2c5ed2f7c"}}, "lastModifiedDateTime": "2019-02-13T01:41:27Z", "parentReference": {"id": "e0643a79-f57f-413c-a659-181451640d9b", "siteId": "example.sharepoint.com,6f577f5b-d764-41bb-87eb-0e759bf7adad,e3690907-0af7-4fab-8d5b-01f7966f180b"}, "webUrl": "https://example.sharepoint.com/sites/new-sc/Shared%20Documents/test.docx"}Oddly, it isn’t possible to use the following OData query parameters in the above request: params = "$expand=driveItem&$select=driveItem,sharepointIds"1params = "$expand=driveItem&$select=driveItem,sharepointIds"This returns a malformed JSON string (likely a bug with the Graph API), that contains the sharepointIds and an error message: '{"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.list)(\'5aa9863e-8c76-4732-8c3f-bbf91f96027f\')/items(driveItem,sharepointIds)/$entity","@odata.etag":"\\"91968a70-005c-48e1-87c5-00d84b9d3b4f,4\\"","sharepointIds":{"listId":"5aa9863e-8c76-4732-8c3f-bbf91f96027f","listItemId":"5","listItemUniqueId":"91968a70-005c-48e1-87c5-00d84b9d3b4f","siteId":"6f577f5b-d764-41bb-87eb-0e759bf7adad","siteUrl":"https://example.sharepoint.com/sites/new-sc","webId":"e3690907-0af7-4fab-8d5b-01f7966f180b"}{\r\n "error": {\r\n "code": "BadRequest",\r\n "message": "The entity instance value of type \'microsoft.graph.listItem\' doesn\'t have a value for property \'id\'. To compute an entity\'s metadata, its key and concurrency-token property values must be provided.",\r\n "innerError": {\r\n "request-id": "59766e81-966d-4dab-852e-371c2c6f72c4",\r\n "date": "2019-02-13T04:07:09"\r\n }\r\n }\r\n}'1'{"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.list)(\'5aa9863e-8c76-4732-8c3f-bbf91f96027f\')/items(driveItem,sharepointIds)/$entity","@odata.etag":"\\"91968a70-005c-48e1-87c5-00d84b9d3b4f,4\\"","sharepointIds":{"listId":"5aa9863e-8c76-4732-8c3f-bbf91f96027f","listItemId":"5","listItemUniqueId":"91968a70-005c-48e1-87c5-00d84b9d3b4f","siteId":"6f577f5b-d764-41bb-87eb-0e759bf7adad","siteUrl":"https://example.sharepoint.com/sites/new-sc","webId":"e3690907-0af7-4fab-8d5b-01f7966f180b"}{\r\n "error": {\r\n "code": "BadRequest",\r\n "message": "The entity instance value of type \'microsoft.graph.listItem\' doesn\'t have a value for property \'id\'. To compute an entity\'s metadata, its key and concurrency-token property values must be provided.",\r\n "innerError": {\r\n "request-id": "59766e81-966d-4dab-852e-371c2c6f72c4",\r\n "date": "2019-02-13T04:07:09"\r\n }\r\n }\r\n}'This means that it isn’t possible to obtain both the Drive Item metadata as well as the SharePoint IDs in a single request. We’ll see how to solve this with the v2.0 API later below.OneDrive for BusinessTo retrieve the data above on items in ODB rather than SPO, replace the site-path with the path to a user’s personal site, and ensure the hostname points to ODB (e.g. company-my.sharepoint.com): curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example-my.sharepoint.com:/personal/user_example_com:/lists/1234/items/5678?$expand=driveItem'1curl -s -H "$AUTH" 'https://graph.microsoft.com/v1.0/sites/example-my.sharepoint.com:/personal/user_example_com:/lists/1234/items/5678?$expand=driveItem'CRUD operationsPerform DELETE, PATCH, and PUT operations directly on the driveItem as shown below, although it is possible to perform them on the listItem as well: curl -s -H "$AUTH" -X DELETE 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/lists/5aa9863e-8c76-4732-8c3f-bbf91f96027f/items/91968a70-005c-48e1-87c5-00d84b9d3b4/driveItem'1curl -s -H "$AUTH" -X DELETE 'https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/new-sc:/lists/5aa9863e-8c76-4732-8c3f-bbf91f96027f/items/91968a70-005c-48e1-87c5-00d84b9d3b4/driveItem'Using v2.0 of the SPO REST APIIt turns out the v2.0 of the SPO REST API allows us to retrieve both the sharepointIds object and the driveItem information in a single request: curl -s -H "$AUTH" 'https://example.sharepoint.com/_api/v2.0/sites/example.sharepoint.com:/sites/new-sc:/items/91968a70-005c-48e1-87c5-00d84b9d3b4?$expand=driveItem&$select=driveItem,sharepointIds'1curl -s -H "$AUTH" 'https://example.sharepoint.com/_api/v2.0/sites/example.sharepoint.com:/sites/new-sc:/items/91968a70-005c-48e1-87c5-00d84b9d3b4?$expand=driveItem&$select=driveItem,sharepointIds'Note that this type of request was returning malformed data when using the Graph API. The v2.0 SPO REST API treats the sharepointIds field as a navigable property. The Graph API treats it as a normal property that can’t be extended.The requests above also work with ODB as mentioned previously.Similarly, the driveItem also allows DELETE, PATCH, and PUT operations on it: curl -s -H "$AUTH" -X DELETE 'https://example.sharepoint.com/_api/v2.0/sites/example.sharepoint.com:/sites/new-sc:/items/91968a70-005c-48e1-87c5-00d84b9d3b4/driveItem'1curl -s -H "$AUTH" -X DELETE 'https://example.sharepoint.com/_api/v2.0/sites/example.sharepoint.com:/sites/new-sc:/items/91968a70-005c-48e1-87c5-00d84b9d3b4/driveItem'Thoughts on the Graph and SPO REST APIsBoth the Graph API and the v2.0 SPO REST API interfaces constantly change. This results in some surprises since certain requests may cease to work or begin working unexpectedly. The requests above provide examples of both scenarios. Unfortunately, the documentation doesn’t clarify which features function as expected; if a request doesn’t work with the v1.0 Graph API, give the beta version a try.How Kloudless helpsSimilar to the Graph API, the Kloudless API provides a single interface to Office 365 SPO and ODB data. Rather than investigate the specifics of each of those APIs, use Kloudless to quickly integrate over 20 different cloud storage services with a single implementation. Kloudless also unifies the Graph API and SPO API data to ensure it returns the maximum amount of data available. Sign up for Kloudless here!