31 Oct 2023

Transfer files between hubs, projects, folders

Default blog image

Note: the following only works when the source and target hubs are in the same region, and so the same underlying bucket is used. In case of different regions you'd have to download the source file and upload it to the new location without using PUT /copyTo

Though I already wrote about moving files around in the blog post Move files around on Forge, it was not clear from there that actually, you can use the fairly unknown PUT /copyTo endpoint in case of user storage accounts (BIM 360 Docs, Autodesk Docs, Fusion Team) as well, since they all use the same bucket underneath. 

The main steps of uploading a file to a user storage account (also mentioned here) are the following:

1) Create a storage location in the bucket the user storage is using (e.g. wip.dm.prod)

2) Upload the content to that location

3) Create the first or a new file version

What might not be obvious is that when doing step #2 you can also call the PUT /copyTo endpoint to reuse the content of an existing file to populate the content of a new file.
You just need to get the storage location of the source file as shown here.

I also tested this by moving content from a BIM 360 hub to a Fusion Team hub, and that worked too. 

Here is a Node.js sample using express framework that you could simply add to an existing application for testing.
I left the values I used in there so it's easier to see what exact content is needed.

app.get("/filecopy", async (req, res) => {
  const _clientId = "---";
  const _clientSecret = "---";
  const _callbackUrl = "http://localhost:3000/filecopy";
  const _sourceProjectId = "b.63aa5109-849e-46cf-8b75-1cded97246f4"; 
  const _targetProjectId = "a.YnVzaW5lc3M6YXV0b2Rlc2szNzQzIzIwMTkxMDA5MjI0NzUyODUx"; 
  const _sourceFileUrn = "urn:adsk.wipprod:dm.lineage:Ac7QjaI6T0K30D8e3dsGMQ"; 
  const _targetFolderUrn = "urn:adsk.wipprod:fs.folder:co.os-cZlcASOGHZplrihDAxg"; 
  const APS = require("forge-apis");

  // Get access token

  const authApi = new APS.AuthClientThreeLegged(
    _clientId, _clientSecret, _callbackUrl, 
    ["data:read", "data:write", "data:create"]); 

  let code = req.query.code;
  if (!code) {

  const credentials = await authApi.getToken(code);

  // Get source file storage ID

  const itemsApi = new APS.ItemsApi();
  const sourceVersion = await itemsApi.getItemTip(
    _sourceProjectId, _sourceFileUrn, null, credentials);
  const sourceStorageId = sourceVersion.body.data.relationships.storage.data.id;
  const sourceStorageName = sourceStorageId.split("/")[1];
  const storageBucket = sourceStorageId.split("/")[0].split(":os.object:")[1]; // usually "wip.dm.prod"
  const sourceStorageExtension = sourceStorageName.split(".")[1];
  let sourceFileName = sourceVersion.body.data.attributes.displayName;

  // Needed because of Fusion 360 models that have no extension in the display name
  if (!sourceFileName.endsWith(sourceStorageExtension)) {
    sourceFileName += "." + sourceStorageExtension;

  // Create storage in target folder

  const projectsApi = new APS.ProjectsApi();
  const targetStorage = await projectsApi.postStorage(_targetProjectId, {
    jsonapi: {
      version: "1.0"
    data: {
      type: "objects",
      attributes: {
        name: sourceStorageName,
      relationships: {
        target: {
          data: {
            type: "folders",
            id: _targetFolderUrn
  }, null, credentials);
  const targetStorageId = targetStorage.body.data.id;
  const targetStorageName = targetStorageId.split("/")[1];

  // Transfer data from source to target storage

  const objectsApi = new APS.ObjectsApi();
  const transfer = await objectsApi.copyTo(
    storageBucket, sourceStorageName, targetStorageName, null, credentials);

  // Create first version

  try {
    const newVersion = await itemsApi.postItem(_targetProjectId, {
      "jsonapi": { "version": "1.0" },
      "data": {
        "type": "items",
        "attributes": {
          "displayName": sourceFileName,
          "extension": {
            "type": "items:autodesk.core:File", 
            "version": "1.0"
        "relationships": {
          "tip": {
            "data": {
              "type": "versions", "id": "1"
          "parent": {
            "data": {
              "type": "folders",
              "id": _targetFolderUrn
      "included": [
          "type": "versions",
          "id": "1",
          "attributes": {
            "name": sourceFileName,
            "extension": {
              "type": "versions:autodesk.core:File", 
              "version": "1.0"
          "relationships": {
            "storage": {
              "data": {
                "type": "objects",
                "id": targetStorageId
    }, null, credentials);
  } catch (error) {

  // Done


You need to use different item and version type depending on where you are uploading the file: versions:autodesk.core:File / versions:autodesk.bim360:File / versions:autodesk.bim360:Document

Note that a Fusion 360 design has the type versions:autodesk.fusion360:Design, but you cannot upload such a file using the public APIs. You can only upload it to Fusion Team as versions:autodesk.core:File, in which case the content will be considered an f3d archive and you still need to turn it into a Fusion Design (see pic) before you can use it inside Fusion 360. Depending on your requirements this might be an acceptable compromise.


Related Article