The CreateCopyJobs API, copy or move SharePoint files or folders


This is overview on how the Office 365 CreateCopyJobs API (/_api/site/CreateCopyJobs) works from my observations. For the past months we are seeing new Copy and Move buttons on the SharePoint modern document library actions bar. The APIs behind can copy or move files or folders across different SharePoint site collections or to OneDrive for Business. Here is what I discovered about the APIs.

CreateCopyJobs api used by SharePoint user interface

Move a file using the CreateCopyJobs APIs

First step is to send post request with payload about the file to the CreateCopyJobs endpoint i.e. /_api/site/CreateCopyJobs (POST)


https://my_tenant.sharepoint.com/sites/my_site/_api/site/CreateCopyJobs

Here is how my post request payload looks like


// url: https://my_tenant.sharepoint.com/sites/my_site/_api/site/CreateCopyJobs
// headers: Authentication: "Bearer your_access_token"
// body (json):
{  
   "exportObjectUris":[  
      "https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/Test.docx"
   ],
   "destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
   "options":{  
      "IgnoreVersionHistory":true,
      "IsMoveMode":true
   }
}

Here is the response we get from the HTTP call


{  
   "d":{  
      "CreateCopyJobs":{  
         "__metadata":{  
            "type":"Collection(SP.CopyMigrationInfo)"
         },
         "results":[  
            {  
               "EncryptionKey":"JjjU0CBBg3NmTI6SqxcV/zlSgYStNF4KeR+0MLVUtCw=",
               "JobId":"bdc524a5-7277-4699-9af3-64a06f5fb2ed",
               "JobQueueUri":"https://sposn1bn1m021pr.queue.core.windows.net:443/1247pq20181029-6c611e06b4ae499d9e59e3590b193ba2?sv=2017-07-29&sig=KnsThUylL671QsV%2F8lh10LtsApEBdcli80b0%2FDyKZao%3D&st=2018-10-28T07%3A00%3A00Z&se=2018-11-19T07%3A00%3A00Z&sp=rap",
               "SourceListItemUniqueIds":{  
                  "__metadata":{  
                     "type":"Collection(Edm.Guid)"
                  },
                  "results":[  

                  ]
               }
            }
         ]
      }
   }
}

Once the request is sent, it goes in a queue and we can check the status of the queue by calling another SharePoint endpoint i.e. /_api/site/GetCopyJobProgress (GET).


https://my_tenant.sharepoint.com/sites/my_site/_api/site/GetCopyJobProgress

So, we have to construct a request so the /_api/site/GetCopyJobProgress to call the right Azure queue instance using the response data from the CreateCopyJobs APIs above. Here is the JSON payload for the /_api/site/GetCopyJobProgress GET request. We can see how the data is mapped comparing the above response data with the below request payload:


{  
   "copyJobInfo":{  
      "EncryptionKey":"JjjU0CBBg3NmTI6SqxcV/zlSgYStNF4KeR+0MLVUtCw=",
      "JobId":"bdc524a5-7277-4699-9af3-64a06f5fb2ed",
      "JobQueueUri":"https://sposn1bn1m021pr.queue.core.windows.net:443/1247pq20181029-6c611e06b4ae499d9e59e3590b193ba2?sv=2017-07-29&sig=KnsThUylL671QsV%2F8lh10LtsApEBdcli80b0%2FDyKZao%3D&st=2018-10-28T07%3A00%3A00Z&se=2018-11-19T07%3A00%3A00Z&sp=rap",
      "SourceListItemUniqueIds":{  
         "__metadata":{  
            "type":"Collection(Edm.Guid)"
         },
         "results":[  

         ]
      }
   }
}

From the payload is very obvious that my copy job was hosted to a server where the JobQueueUri is the server, then I have the Id or the job JobId and also a key EncryptionKey to be able to authenticate and check the job status.

GetCopyJobProgress API - Different statuses and responses

So, we have to poll the GetCopyJobProgress API until we get job status success, but here is first the response you'd expect when the job is in progress.

Job in the queue, the "JobState" is 4

From my observation when you get "JobState" = 4, then it is still in the queue and in progress.


{  
   "d":{  
      "GetCopyJobProgress":{  
         "__metadata":{  
            "type":"SP.CopyJobProgress"
         },
         "JobState":4,
         "Logs":{  
            "__metadata":{  
               "type":"Collection(Edm.String)"
            },
            "results":[  

            ]
         }
      }
   }
}

Note, pay attention on the results node, sometimes there is a log of actions happened and that information might be of interest to you.

Job success, the "JobState" is 0, but should not have any errors in the results node of the response


{
   "d":{
      "GetCopyJobProgress":{
         "__metadata":{
            "type":"SP.CopyJobProgress"
         },
         "JobState":0,
         "Logs":{
            "__metadata":{
               "type":"Collection(Edm.String)"
            },
            "results":[
               ...
               "{\r\n  \"Event\": \"JobWarning\",\r\n  \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n  \"Time\": \"10/29/2018 22:15:41.652\",\r\n  \"TotalRetryCount\": \"0\",\r\n  \"MigrationType\": \"Copy\",\r\n  \"MigrationDirection\": \"Import\",\r\n  \"ObjectType\": \"ListItem\",\r\n  \"Url\": \"Shared Documents/Test2.docx\",\r\n  \"Id\": \"eb14999e-0e81-4726-be44-e9eb51ef0562\",\r\n  \"SourceListItemIntId\": \"255\",\r\n  \"TargetListItemIntId\": \"255\",\r\n  \"Message\": \"Field HasCar cannot be found. Field value will not be imported\",\r\n  \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
               "{\r\n  \"Event\": \"JobEnd\",\r\n  \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n  \"Time\": \"10/29/2018 22:15:42.970\",\r\n  \"FilesCreated\": \"3\",\r\n  \"BytesProcessed\": \"63874\",\r\n  \"ObjectsProcessed\": \"2\",\r\n  \"TotalExpectedSPObjects\": \"2\",\r\n  \"TotalErrors\": \"0\",\r\n  \"TotalWarnings\": \"18\",\r\n  \"TotalRetryCount\": \"0\",\r\n  \"MigrationType\": \"Copy\",\r\n  \"MigrationDirection\": \"Import\",\r\n  \"WaitTimeOnSqlThrottlingMilliseconds\": \"0\",\r\n  \"CreatedOrUpdatedFileStatsBySize\": \"{\\\"10-100K\\\":{\\\"Count\\\":3,\\\"TotalSize\\\":63874,\\\"TotalDownloadTime\\\":0,\\\"TotalCreationTime\\\":2986}}\",\r\n  \"ObjectsStatsByType\": \"{\\\"SPUser\\\":{\\\"Count\\\":1,\\\"TotalTime\\\":0,\\\"AccumulatedVersions\\\":0,\\\"ObjectsWithVersions\\\":0},\\\"SPFile\\\":{\\\"Count\\\":1,\\\"TotalTime\\\":3015,\\\"AccumulatedVersions\\\":3,\\\"ObjectsWithVersions\\\":1},\\\"SPListItem\\\":{\\\"Count\\\":1,\\\"TotalTime\\\":5532,\\\"AccumulatedVersions\\\":3,\\\"ObjectsWithVersions\\\":1}}\",\r\n  \"TotalExpectedBytes\": \"63874\",\r\n  \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
               "{\r\n  \"Event\": \"JobFinishedObjectInfo\",\r\n  \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n  \"Time\": \"10/29/2018 22:15:42.970\",\r\n  \"SourceObjectFullUrl\": \"https://tenant1.sharepoint.com/sites/site1/Shared Documents/Test.docx\",\r\n  \"TargetServerUrl\": \"https://tenant1.sharepoint.com\",\r\n  \"TargetSiteId\": \"45fdccd8-47f3-4870-bcb0-7aaabfcd52f9\",\r\n  \"TargetWebId\": \"d6d96969-217f-4306-b15b-fe35b6b754cc\",\r\n  \"TargetListId\": \"c1880f57-266c-47cd-b5ae-cc111ca2e8ce\",\r\n  \"TargetObjectUniqueId\": \"a20fe3a1-9201-427b-9eb4-9b2c03c5710a\",\r\n  \"TargetObjectSiteRelativeUrl\": \"Shared Documents/Test2.docx\",\r\n  \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
               "{\r\n  \"Event\": \"JobStart\",\r\n  \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n  \"Time\": \"10/29/2018 22:15:42.996\",\r\n  \"SiteId\": \"692102df-335d-41e2-aa44-425b626037ea\",\r\n  \"WebId\": \"f7fb12c3-ca68-4060-b1b0-c27a6bfffeb2\",\r\n  \"DBId\": \"eb30ff26-a12c-431e-bb10-68fdac21ce28\",\r\n  \"FarmId\": \"cd8ae8dc-97d0-4042-8197-64044cfecae0\",\r\n  \"ServerId\": \"f7e14548-d5a5-4108-a544-2f667ea1f7fc\",\r\n  \"SubscriptionId\": \"ea1787c6-7ce2-4e71-be47-5e0deb30f9e4\",\r\n  \"TotalRetryCount\": \"0\",\r\n  \"MigrationType\": \"Copy\",\r\n  \"MigrationDirection\": \"MoveCleanup\",\r\n  \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
               "{\r\n  \"Event\": \"JobEnd\",\r\n  \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n  \"Time\": \"10/29/2018 22:15:43.012\",\r\n  \"TotalRetryCount\": \"0\",\r\n  \"MigrationType\": \"Copy\",\r\n  \"MigrationDirection\": \"MoveCleanup\",\r\n  \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}"
            ]
         }
      }
   }
}

Job done with errors, the "JobState" is still 0, but there is a JobError in the results node of the response

Here is the response data and the "JobState" is still 0, but we have to pay attention on the results log where we have errors. So, "JobState" equals 0, does not mean successfully completed.


{  
   "d":{  
      "GetCopyJobProgress":{  
         "__metadata":{  
            "type":"SP.CopyJobProgress"
         },
         "JobState":0,
         "Logs":{  
            "__metadata":{  
               "type":"Collection(Edm.String)"
            },
            "results":[  
               "{\r\n  \"Event\": \"JobStart\",\r\n  \"JobId\": \"bdc524a5-7277-4699-9af3-64a06f5fb2ed\",\r\n  \"Time\": \"10/29/2018 22:12:22.769\",\r\n  \"SiteId\": \"692102df-335d-41e2-aa44-425b626037ea\",\r\n  \"WebId\": \"f7fb12c3-ca68-4060-b1b0-c27a6bfffeb2\",\r\n  \"DBId\": \"eb30ff26-a12c-431e-bb10-68fdac21ce28\",\r\n  \"FarmId\": \"cd8ae8dc-97d0-4042-8197-64044cfecae0\",\r\n  \"ServerId\": \"c327d744-babf-486e-944d-e2723b999830\",\r\n  \"SubscriptionId\": \"ea1787c6-7ce2-4e71-be47-5e0deb30f9e4\",\r\n  \"TotalRetryCount\": \"0\",\r\n  \"MigrationType\": \"Copy\",\r\n  \"MigrationDirection\": \"MoveCleanup\",\r\n  \"CorrelationId\": \"f6409d9e-c040-7000-5b4f-0367f426565b\"\r\n}",
               "{\r\n  \"Event\": \"JobError\",\r\n  \"JobId\": \"bdc524a5-7277-4699-9af3-64a06f5fb2ed\",\r\n  \"Time\": \"10/29/2018 22:12:22.769\",\r\n  \"TotalRetryCount\": \"0\",\r\n  \"MigrationType\": \"Copy\",\r\n  \"MigrationDirection\": \"MoveCleanup\",\r\n  \"ObjectType\": \"File\",\r\n  \"Url\": \"Shared Documents/Test.docx\",\r\n  \"Id\": \"3e1cdc58-149b-40f3-a705-b1217161e70c\",\r\n  \"SourceListItemIntId\": \"255\",\r\n  \"ErrorCode\": \"-2147024713\",\r\n  \"Message\": \"A file or folder with the name 'Test.docx' already exists at the destination.\",\r\n  \"CorrelationId\": \"f6409d9e-c040-7000-5b4f-0367f426565b\"\r\n}",
               "{\r\n  \"Event\": \"JobEnd\",\r\n  \"JobId\": \"bdc524a5-7277-4699-9af3-64a06f5fb2ed\",\r\n  \"Time\": \"10/29/2018 22:12:22.769\",\r\n  \"TotalRetryCount\": \"0\",\r\n  \"MigrationType\": \"Copy\",\r\n  \"MigrationDirection\": \"MoveCleanup\",\r\n  \"CorrelationId\": \"f6409d9e-c040-7000-5b4f-0367f426565b\"\r\n}"
            ]
         }
      }
   }
}

Note, we will have to parse the result node to JSON and try find any errors. In this scenario we have the following error: "A file or folder with the name 'Test.docx' already exists at the destination."

Here is the MigrationJobState from the Microsoft.SharePoint.Client DLL:


namespace Microsoft.SharePoint.Client
{
  public enum MigrationJobState
  {
    None = 0,
    Queued = 2,
    Processing = 4,
  }
}

Options that we can specify in a CreateCopyJobs request

I used the SharePoint OData metadata and the CSOM dll to get some more information and following options are available at the moment:


<ComplexType Name="CopyMigrationOptions">
    <Property Name="AllowSchemaMismatch" Type="Edm.Boolean" Nullable="false"/>
    <Property Name="AllowSmallerVersionLimitOnDestination" Type="Edm.Boolean" Nullable="false"/>
    <Property Name="IgnoreVersionHistory" Type="Edm.Boolean" Nullable="false"/>
    <Property Name="IsMoveMode" Type="Edm.Boolean" Nullable="false"/>
    <Property Name="NameConflictBehavior" Type="Edm.Int32" Nullable="false"/>
</ComplexType>

Here is the enum for NameConflictBehavior pulled from the Microsoft.SharePoint.Client dll:


namespace Microsoft.SharePoint.Client
{
  public enum MigrationNameConflictBehavior
  {
    Fail,
    Replace,
    Rename,
  }
}
  • AllowSchemaMismatch: allows file move with missing or mismatched field definition on the destination list for source field. More clarification bellow.
  • IgnoreVersionHistory: ignores version history when moves the file and will create only one version (1.0) on the target location
  • IsMoveMode: true moves the file, false creates a copy of the file
  • NameConflictBehavior: 1 = REPLACE and 2 = KEEP BOTH
  • Copy a file using the CreateCopyJobs API

    Copying a file follows the same rules to call CreateCopyJobs endpoint i.e. /_api/site/CreateCopyJobs (POST), but the payload send to the API has set the "IsMoveMode" to false or not specified at all. Here is how my post request payload looks like:

    
    {  
       "exportObjectUris":[  
          "https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/Test.docx"
       ],
       "destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
       "options":{  
          "IgnoreVersionHistory":true,
          "IsMoveMode":false
       }
    }
    

    That is followed by polling the /_api/site/GetCopyJobProgress to get the status of the job. The same process as the described above for file move.

    Copy or move a folder using the CreateCopyJobs API

    The exact same process applies when moving or copying folder, but the request payload differs where we have to specify folder instead of file. Here is an example:

    
    {  
       "exportObjectUris":[  
          "https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/TestFolder1"
       ],
       "destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
       "options":{  
          "IgnoreVersionHistory":true,
          "IsMoveMode":true,
          "AllowSchemaMismatch":true
       }
    }
    

    Important: Folder cannot be moved using MigrationNameConflictBehavior = Replace, but just with MigrationNameConflictBehavior = Rename

    CreateCopyJobs api folder keep both option only

    Tips on how to solve common errors that might popup during move or copy

    How to solve GetCopyJobProgress error: A file or folder with the name 'Test.docx' already exists at the destination

    There is "NameConflictBehavior" option that can be specified with the payload to prevent conflict between the file to be moved and a target file with the same name. Allowed values are 1 = REPLACE and 2 = KEEP BOTH.

    
    {  
       "exportObjectUris":[  
          "https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/Test.docx"
       ],
       "destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
       "options":{  
          "IgnoreVersionHistory":true,
          "IsMoveMode":true,
          "NameConflictBehavior":2
       }
    }
    

    CreateCopyJobs sends response error 500, mismatched field definition on the destination list

    So, the CreateCopyJobs will send back immediately response 500 when there is a list, field schema definition mismatch. For example, you are trying to copy file with content type 'Picture', but the content type 'Picture' is not available on the target library then the API will error because there might be a possible data loss and will not proceed. Here is the response payload:

    
    {  
       "error":{  
          "code":"-2147213282, Microsoft.SharePoint.SPException",
          "message":{  
             "lang":"en-US",
             "value":"Missing or mismatched field definition on the destination list for source field 'ImageWidth' type 'Integer'. Source site template id 'GroupWebTemplateID', target site template id 'SitePagePublishing'. Total blocked root objects in this operation is [1]."
          }
       }
    }
    

    If you still want to proceed and move the document you can create another /_api/site/CreateCopyJobs (POST) request, but specify in the payload "AllowSchemaMismatch":true. Example:

    
    {  
       "exportObjectUris":[  
          "https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/Test.docx"
       ],
       "destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
       "options":{  
          "IgnoreVersionHistory":true,
          "IsMoveMode":true,
          "AllowSchemaMismatch":true,
          "NameConflictBehavior":2
       }
    }
    

    Is the CreateCopyJobs API supported?

    Yes and no, maybe not yet :) . Well the SharePoint Client Side Object DLL has it and we can use it in backed solutions, but in the same time is not officially documented which means Microsoft might not support you in a case of failure.

    Benefits of the CreateCopyJobs API

  • Performs copy or move on Azure servers, so no download-upload stream is happening from the machine where the script is executed.
  • Built-in retry mechanisms so we do not have to implement additional logic for that.
  • It is cross site collection, including OneDrive for Business. Previously, we had copy move util API working only within the site collection of the file to be copied.
  • It can copy and move ASPX pages from SitePages library to SitePages library although it is not exposed form the SitePages library UI.
  • Batch jobs are possible, but not covered in this blog.
  • Conclusion

    Well, I find it handy and it is one API for copy and move together. I will keep an eye on the API and how it evolves in future. The previous time I had a look there wasn't an option for NameConflictBehavior and this was few months ago. That means that they rapidly update it with new features.

    Posted on

    Tags: CreateCopyJobs API, GetCopyJobProgress API, SharePoint Online, Office 365, OneDrive for Business, Copy Move API, SPFx, SharePoint Framework, Modern SitePages Library

    Comments