Developer

Upload content into a Canvas for v3

Objective: Upload content from a local drive into a workspace.

The currently supported file extensions for uploading content to a Workspace are:

Type Allowed extensions Maximum size to upload (default)
Images png, jpeg, gif, tiff, pdf, docx, pptx, xlsx, doc, ppt, xls 50 Mb
Documents doc, docx, ppt, pptx, xls, xlsx, pdf, docMime, docxMime, pptMime, pptxMime, xlsMime, xlsxMime, pdfMime 50 Mb
Videos mp4, mov, m4v, quicktime, mp4Mime, m4vMime, quicktimeMime 150 Mb

This page contains the implementation details for REST APIs v3 and for GraphQL APIs, and you can see the page with details for the v2 REST APIs.
Please check the previous guide in how to create a Canvas and add a Text Element to see how to create a Canvas and obtain this new Canvas Id. You will need the Canvas Id to position the content to upload inside this Canvas.

The upload of content into a workspace by APIs can be done in 2 ways:

Upload from local using REST APIs

Upload from local In REST APIs v3, the upload process from your local drive is divided in 3 steps:

  1. Create a zygote or placeholder for the file to upload. Here you will get the credentials to upload the file to a storage service, Amazon S3 in the example.
  2. Upload the image to Amazon S3
  3. Link the zygote or placeholder to the uploaded image: it finishes the upload into the workspace

STEP 1: Create zygote or placeholder for the file to upload

This is the first step of three for the upload, for both REST and GraphQL APIs. You will need to create a zygote or placeholder, in the workspace, for the file to upload. This step will generate the Id of the object and the credentials for doing the actual upload of the file. See the details below.

STEP 1, implementation using REST API

REST API /v3/workspaces/<workspaceId>/elements
Method POST
Comments <CANVAS_ID> is the ID of the preciously created canvas. This is the canvas where you want to add the uploaded content. The position of the content to upload will be in reference to the top-left corner of this Canvas.

You need to specify the type of the file you will upload. Specify it in the imageFormat field, according to the specific type of image you are uploading.
You will also need to assign, the values for the filename and a title fields, set as "myImageToUpload.jpg" in the following example:

# The default "width" and "height" value is 1000. 
# If your image is smaller than 1000x1000 pixels, and "width" and "height" are not specified, 
# your image will be vertically and horizontally centered within a 1000x1000 placeholder space. 
curl --location --request POST '/v3/workspaces/<SET_WORKSPACEID>/elements?relativeToOriginOf=<CANVAS_ID>' \
--header 'Authorization: Bearer <SET_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "type": "Image",
    "filename": "myImageToUpload.jpg",
    "title": "myImageToUpload.jpg",
    "imageFormat": "jpg",
    "transform": { "x": 100, "y": 100, "scale": 1 }
  }'

const request = require('request-promise');
const fs = require('fs');

const token = '<SET_TOKEN>'
const portal = '';
const workspaceId = '<WORKSPACE_ID>';
const api_version = '/v3';
const filePath = '<PATH_TO_IMAGE>';

const request_headers = {
    'Authorization': "Bearer " + token,
    'Content-Type' : 'application/json'    
};

const zygote_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/elements?relativeToOriginOf=<CANVAS_ID>';
// The default "width" and "height" value is 1000. 
// If your image is smaller than 1000x1000 pixels, and "width" and "height" are not specified, 
// your image will be vertically and horizontally centered within a 1000x1000 placeholder space.  
const zygote_body = {
    "type": "Image",
    "filename": "myImageToUpload.jpg",
    "title": "myImageToUpload.jpg",
    "imageFormat": "jpg",
    "transform": { "x": 100, "y": 100, "scale": 1 }
}
const zygote_options = {
    headers: request_headers,
    uri: zygote_endpoint,
    method: 'POST',
    json: zygote_body
}

const zygote_response = await request(zygote_options)
.catch(err => console.log(err))

console.log(zygote_response)
const zygote_data = zygote_response.data.content;

import requests
import pprint

token = '<SET_TOKEN>'
portal = ''
workspaceId = '<WORKSPACE_ID>';
api_version = '/v3';
filePath = '<PATH_TO_IMAGE>';

request_headers = {
    'Authorization': "Bearer " + token,
    'Content-Type' : 'application/json'    
}

if __name__ == "__main__":
    zygote_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/elements?relativeToOriginOf=<CANVAS_ID>'
    # The default "width" and "height" value is 1000. 
    # If your image is smaller than 1000x1000 pixels, and "width" and "height" are not specified, 
    # your image will be vertically and horizontally centered within a 1000x1000 placeholder space. 
    zygote_body = {
        "type": "Image",
        "filename": "myImageToUpload.jpg",
        "title": "myImageToUpload.jpg",
        "imageFormat": "jpg",
        "transform": { "x": 100, "y": 100, "scale": 1 }
    }

    zygote_request = requests.post(zygote_endpoint, headers = request_headers, json = zygote_body)
    zygote_response = zygote_request.json()
    pprint.pprint(zygote_response)

    zygote_data = zygote_response['data']['content']

The response body you will get should look like this:

{
    "data": {
        "content": {
            "uploadId": "60aed93111bf08bf22a2572a",
            "url": "https://s3.us-east-1.amazonaws.com/acceptance-new.public-assets.bluescape.com",
            "fields": {
                "key": "sessions/objects/GDUWtDvSZGtE868PBBk2/60aed93111bf08bf22a2572a.jpg",
                "bucket": "acceptance-new.public-assets.bluescape.com",
                "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
                "X-Amz-Credential": "AKIZIVWWSYKG79KFMQJA/20210526/us-east-1/s3/aws4_request",
                "X-Amz-Date": "20210526T232643Z",
                "Policy": "eyJleHNpcmF0aW9uIjoiMjAyMS0wNS0yNlQyMzozMTo0MVoiLCJjb25kaXRpb25zIjpbWyJlcSIsIiRrZXkiLCJzZXNzaW9ucy9vYmplY3RzL0dEVVd0RHZTWkd0Nzg2OFBCQmsyLzYwYWVkOTMxMTdiZjA4YmYyMmEyNTcyYS5qcGciXSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwwLDUyNDI4ODAwXSx7ImJ1Y2tldCI6ImFjY2VwdGFuY2Utbmv3LnB1YmxpYy1hc3NldHMuYmx1ZXNjYXBlLmNvbSJ9LHsiWC1BbXotQWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJVldXU1lLRzdES0ZNUUpBLzIwMjEwNTI2L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IlgtQW16LURhdGUiOiIyMDIxMDUyNlQyMzI2NDFaIn1dfQ==",
                "X-Amz-Signature": "fd521d7b3416a12a9997a2e1a10408f7P8427d9d0ae01A53e65a926f0f07a56d"
            }
        },
        "image": {
            "id": "60aed93111bf08bf22a2572a",
            "zIndex": 1183,
            "transform": {
                "x": 300,
                "y": 100,
                "scaleX": 1,
                "scaleY": 1
            },
            "traits": {
                "http://bluescape.dev/zygote/v1/ingestionState": {
                    "http://bluescape.dev/zygote/v1/ingestionState/timestamp": 1622071601170,
                    "http://bluescape.dev/zygote/v1/ingestionState/stage": "transferring"
                }
            },
            "pinned": false,
            "comments": [],
            "width": 1000,
            "height": 1000,
            "title": "myImageToUpload.jpg",
            "filename": "myImageToUpload.jpg",
            "ingestionState": "transferring",
            "type": "Image",
            "asset": {
                "imageFormat": "jpeg"
            }
        }
    }
}

At this moment, you will see a placeholder element in the workspace, in the position you specified, displaying a "transferring" message (as shown in the traits in the response body).
From the response body, you will need to use the following fields for the next step:

Field Description
data/content/url Destination URL to where the file will be uploaded.
data/image/id This is the Id of the cygote/placeholder created for the file to upload. It is the final Id of the uploaded file.
data/content/fields These fields are the ones you will need to authenticate with S3 (or the repository you are using) and to upload the file.

STEP 1, implementation using GraphQL API

For this first step, in GraphQL you can use the following mutations: createDocument, createImage, createVideo.
In these mutations you will need to use the filename field to specify that it is an upload from your local drive, and provide the fullpath to the file to upload.

In the example below, see an example of how to create the zygote for an image, using the createImage mutation, providing a "testimage.jpeg" placeholder for the title and filename fields:

curl --location --request POST '/v3/graphql' \
--header 'Authorization: Bearer <SET_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"#create Image from disk\n#imageFormat: jpeg, jpg, gif, png, tiff, tif, image/jpeg, image/gif, image/png, image/tiff\nmutation createImageFromDisk($ws: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $filename:String! $scale:Float) {\n    createImage(\n          workspaceId: $ws\n          input: {\n            title:$title\n            imageFormat: $imageFormat\n            filename:$filename\n            transform: {\n              x: $x\n              y: $y\n              scale:$scale\n            }\n    }) \n    #return values after creation:    \n    {\n        content{ uploadId url fields}\n        preview{uploadId url fields}\n        image{id width height ingestionState}\n    }\n}","variables":{"ws":"<WORKSPACE_ID>","title":"testimage.jpeg","filename":"testimage.jpeg","imageFormat":"jpeg","previewFormat":"jpeg","x":0,"y":0,"scale":1}}'

var request = require('request');
var options = {
  'method': 'POST',
  'url': '/v3/graphql',
  'headers': {
    'Authorization': 'Bearer <SET_TOKEN>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: `#create Image from disk
#imageFormat: jpeg, jpg, gif, png, tiff, tif, image/jpeg, image/gif, image/png, image/tiff
mutation createImageFromDisk($ws: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $filename:String! $scale:Float) {
    createImage(
          workspaceId: $ws
          input: {
            title:$title
            imageFormat: $imageFormat
            filename:$filename
            transform: {
              x: $x
              y: $y
              scale:$scale
            }
    }) 
    #return values after creation:    
    {
        content{ uploadId url fields}
        preview{uploadId url fields}
        image{id width height ingestionState}
    }
}`,
    variables: {"ws":"<WORKSPACE_ID>","title":"testimage.jpeg","filename":"testimage.jpeg","imageFormat":"jpeg","previewFormat":"jpeg","x":0,"y":0,"scale":1}
  })
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});

import requests

url = "/v3/graphql"

payload="{\"query\":\"#create Image from disk\\n#imageFormat: jpeg, jpg, gif, png, tiff, tif, image/jpeg, image/gif, image/png, image/tiff\\nmutation createImageFromDisk($ws: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $filename:String! $scale:Float) {\\n    createImage(\\n          workspaceId: $ws\\n          input: {\\n            title:$title\\n            imageFormat: $imageFormat\\n            filename:$filename\\n            transform: {\\n              x: $x\\n              y: $y\\n              scale:$scale\\n            }\\n    }) \\n    #return values after creation:    \\n    {\\n        content{ uploadId url fields}\\n        preview{uploadId url fields}\\n        image{id width height ingestionState}\\n    }\\n}\",\"variables\":{\"ws\":\"<WORKSPACE_ID>\",\"title\":\"testimage.jpeg\",\"filename\":\"testimage.jpeg\",\"imageFormat\":\"jpeg\",\"previewFormat\":\"jpeg\",\"x\":0,\"y\":0,\"scale\":1}}"
headers = {
  'Authorization': 'Bearer <SET_TOKEN>',
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

STEP 2: Upload the file to S3

In this step you will use the S3 credentials, generated in the previous step, to upload the file into S3.
This step is the same for both REST and GraphQL APIs. The main difference will be how you provide the "fields" values to upload the file to S3.
Please note that you will run an API call directly to S3 (or the repository you are using), and the credentials for authentication and authorization for the upload are the ones provided in the previous step, in the data/content/fields block.

This is the command to run the upload of the file:

curl --location --request POST '<SET-data/content/url>' \
--form 'key="<SET-data/content/field/key>"' \
--form 'bucket="<SET-data/content/field/bucket>"' \
--form 'X-Amz-Algorithm="<SET-data/content/field/X-Amz-Algorithm>"' \
--form 'X-Amz-Credential="<SET-data/content/field/X-Amz-Credential>"' \
--form 'X-Amz-Date="<SET-data/content/field/X-Amz-Date>"' \
--form 'Policy="<SET-data/content/field/Policy>"' \
--form 'X-Amz-Signature="<SET-data/content/field/X-Amz-Signature>"' \
--form '[email protected]"<SET_FULL_PATH_TO_LOCAL_FILE"'

const uploadToBucket = async (upload_data, file) => {
    const uri = upload_data.url;
    console.log(`Uploading to bucket with url: ${uri}`);
    const formData = {
        key: upload_data.fields.key,
        bucket: upload_data.fields.bucket,
        'X-Amz-Algorithm': upload_data.fields['X-Amz-Algorithm'],
        'X-Amz-Credential': upload_data.fields['X-Amz-Credential'],
        'X-Amz-Date': upload_data.fields['X-Amz-Date'],
        Policy: upload_data.fields.Policy,
        'X-Amz-Signature': upload_data.fields['X-Amz-Signature'],
        file: {
        value: fs.createReadStream(filePath),
        options: {
            filename: file.filename,
            contentType: file.type + '/' + file.imageFormat
            },
        },
    };
    const options = {
        uri,
        method: 'POST',
        formData,
        resolveWithFullResponse: true
        };
    return await request(options)
};

const s3upload_response = await uploadToBucket(zygote_data, zygote_body)
    .catch(err => console.log(err))
    console.log(s3upload_response.statusCode)

print('Updateing to bucket with url: ' + zygote_data['url'])
formData = {
    'key': zygote_data['fields']['key'],
    'bucket': zygote_data['fields']['bucket'],
    'X-Amz-Algorithm': zygote_data['fields']['X-Amz-Algorithm'],
    'X-Amz-Credential': zygote_data['fields']['X-Amz-Credential'],
    'X-Amz-Date': zygote_data['fields']['X-Amz-Date'],
    'Policy': zygote_data['fields']['Policy'],
    'X-Amz-Signature': zygote_data['fields']['X-Amz-Signature'],
}

files = {'file': open(filePath, 'rb')}
s3upload_response = requests.post(zygote_data['url'], data=formData, files=files)
pprint.pprint(s3upload_response.status_code)

This is how the command will look when using the data from Step 1:

curl --location --request POST 'https://s3.us-east-1.amazonaws.com/acceptance-new.public-assets.bluescape.com' \
--form 'key="sessions/objects/GDUWtDvSZGtE868PBBk2/60aed93111bf08bf22a2572a.jpg"' \
--form 'bucket="acceptance-new.public-assets.bluescape.com"' \
--form 'X-Amz-Algorithm="AWS4-HMAC-SHA256"' \
--form 'X-Amz-Credential="AKIZIVWWSYKG79KFMQJA/20210526/us-east-1/s3/aws4_request"' \
--form 'X-Amz-Date="20210526T232643Z"' \
--form 'Policy="eyJleHNpcmF0aW9uIjoiMjAyMS0wNS0yNlQyMzozMTo0MVoiLCJjb25kaXRpb25zIjpbWyJlcSIsIiRrZXkiLCJzZXNzaW9ucy9vYmplY3RzL0dEVVd0RHZTWkd0Nzg2OFBCQmsyLzYwYWVkOTMxMTdiZjA4YmYyMmEyNTcyYS5qcGciXSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwwLDUyNDI4ODAwXSx7ImJ1Y2tldCI6ImFjY2VwdGFuY2Utbmv3LnB1YmxpYy1hc3NldHMuYmx1ZXNjYXBlLmNvbSJ9LHsiWC1BbXotQWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJVldXU1lLRzdES0ZNUUpBLzIwMjEwNTI2L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IlgtQW16LURhdGUiOiIyMDIxMDUyNlQyMzI2NDFaIn1dfQ=="' \
--form 'X-Amz-Signature="fd521d7b3416a12a9997a2e1a10408f7P8427d9d0ae01A53e65a926f0f07a56d"' \
--form '[email protected]"/Users/pj/temp/docs/araucaria.jpg"'

IMPORTANT:

  • If the upload process is successful, you will get a return code 204, then proceed to the last step of the upload.
  • If you get an error on this step, you will need to specify the error code and the error message to the next final step. See more details on the next final step instructions.

STEP 3: Link the zygote or placeholder to the uploaded file

The last step is to link the zygote or placeholder to the file we uploaded in the previous step. For this step we will need the id from data/content/uploadId generated in the response body in step 1.
In case something went wrong on the previous step and the asset was not uploaded successfully (maybe the asset was too big, over the size limit), then you will need to send the error in this step. Otherwise, the zygote will be in permanent "Processing" status until it times out. When you send the error message, the zygote will display an error message and a Cancel button to let you delete the zygote created for the failed upload operation.
Please note that the error code and the error message can be customized, they do not need to follow a particular format and will not be verified by the API.
The image below shows how the zygote will show an error when the asset is too big (a video in this case) and the option to cancel the upload, that will delete the zygote:

STEP 3, implementation using REST API

API /v3/workspaces/<workspaceId>/assets/uploads/<uploadId>
Method PUT
Comments <uploadId> is the element Id created in the first step of the upload.

Examples for when the upload on step 2 was successful:

curl --location --request PUT '/v3/workspaces/<workspaceId>/assets/uploads/<uploadId>' \
--header 'Authorization: Bearer <SET_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{}'

const request = require('request-promise');

const upload_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/assets/uploads/' + zygote_response.data.content.uploadId
const upload_options = {
    headers: request_headers,
    uri: upload_endpoint,
    method: 'PUT',
    json: {},
    resolveWithFullResponse: true
}

const upload_response = await request(upload_options)
.catch(err => console.log(err))

console.log(upload_response.statusCode)

import requests

upload_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/assets/uploads/' + zygote_data['uploadId']
upload_response = requests.put(upload_endpoint, headers= request_headers, json={})
pprint.pprint(upload_response.status_code)

Examples for reporting errors on step 2, to show an error message in the zygote:

# Report error on the upload operation, to make the zygote to display an error message
curl --location --request PUT '/v3/workspaces/<workspaceId>/assets/uploads/<uploadId>' \
--header 'Authorization: Bearer <SET_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{ "errorCode" : "<ERROR-CODE-FROM-STEP-2>", "errorMessage" : "<ERROR-MESSAGE-FROM-STEP-2>"}'

const request = require('request-promise');
// Report error on the upload operation, to make the zygote to display an error message
const upload_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/assets/uploads/' + zygote_response.data.content.uploadId
const upload_options = {
    headers: request_headers,
    uri: upload_endpoint,
    method: 'PUT',
    json: { 
      "errorCode" : "<ERROR-CODE-FROM-STEP-2>",
      "errorMessage" : "<ERROR-MESSAGE-FROM-STEP-2>"
    },
    resolveWithFullResponse: true
}

const upload_response = await request(upload_options)
.catch(err => console.log(err))

console.log(upload_response.statusCode)

import requests
# Report error on the upload operation, to make the zygote to display an error message
upload_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/assets/uploads/' + zygote_data['uploadId']
upload_response = requests.put(upload_endpoint, headers= request_headers, json={ "errorCode" : "<ERROR-CODE-FROM-STEP-2>", "errorMessage" : "<ERROR-MESSAGE-FROM-STEP-2>" })
pprint.pprint(upload_response.status_code)

STEP 3, implementation using GraphQL API

Run the "processAsset" GraphQL mutation to finish the upload process:

mutation linkUploadToZygote($workspaceId: String! $uploadID: String!) {
    processAsset( workspaceId:$workspaceId id:$uploadID input:{})
}
and provide these variables:
{
    "workspaceId": "<WORKSPACE_ID>",
    "uploadID": "<uploadId>"
}

Here are examples of the implementation of the mutation when the upload of step 2 was successful:

curl --location --request POST '/v3/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"\nmutation myProcessAsset($ws: String! $uploadID: String!) {\n    processAsset( workspaceId:$ws id:$uploadID input:{\n    })\n}","variables":{"ws":"<WORKSPACE_ID>","uploadID":"<uploadId>"}}'

var request = require('request');
var options = {
  'method': 'POST',
  'url': '/v3/graphql',
  'headers': {
    'Authorization': 'Bearer <SET_TOKEN>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: `mutation myProcessAsset($ws: String! $uploadID: String!) {
    processAsset( workspaceId:$ws id:$uploadID input:{
    })
}`,
    variables: {"ws":"<WORKSPACE_ID>","uploadID":"<uploadId>"}
  })
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});

import requests

url = "/v3/graphql"

payload="{\"query\":\"\\nmutation myProcessAsset($ws: String! $uploadID: String!) {\\n    processAsset( workspaceId:$ws id:$uploadID input:{\\n    })\\n}\",\"variables\":{\"ws\":\"<WORKSPACE_ID>\",\"uploadID\":\"<uploadId>\"}}"
headers = {
  'Authorization': 'Bearer <SET_TOKEN>',
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

Here are examples of the mutation for reporting errors on step 2, to show an error message in the zygote:

curl --location --request POST '/v3/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"\nmutation myProcessAsset($ws: String! $uploadID: String!) {\n    processAsset( workspaceId:$ws id:$uploadID input:{\n errorCode: "<ERROR-CODE-FROM-STEP-2>" \n errorMessage: "<ERROR-MESSAGE-FROM-STEP-2>" })\n}","variables":{"ws":"<WORKSPACE_ID>","uploadID":"<uploadId>"}}'

var request = require('request');
var options = {
  'method': 'POST',
  'url': '/v3/graphql',
  'headers': {
    'Authorization': 'Bearer <SET_TOKEN>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    query: `mutation myProcessAsset($ws: String! $uploadID: String!) {
    processAsset( workspaceId:$ws id:$uploadID input:{
      errorCode: "<ERROR-CODE-FROM-STEP-2>" 
      errorMessage: "<ERROR-MESSAGE-FROM-STEP-2>"
    })
}`,
    variables: {"ws":"<WORKSPACE_ID>","uploadID":"<uploadId>"}
  })
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});

import requests

url = "/v3/graphql"

payload="{\"query\":\"\\nmutation myProcessAsset($ws: String! $uploadID: String!) {\\n    processAsset( workspaceId:$ws id:$uploadID input:{\\n errorCode: \"<ERROR-CODE-FROM-STEP-2>\" errorMessage: \"<ERROR-MESSAGE-FROM-STEP-2>\"  })\\n}\",\"variables\":{\"ws\":\"<WORKSPACE_ID>\",\"uploadID\":\"<uploadId>\"}}"
headers = {
  'Authorization': 'Bearer <SET_TOKEN>',
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

Upload by URL using REST APIs

An alternative method to upload content into a workspace is to upload by URL. You can use the URL pointing to a resource to upload it into a workspace.
When should we use this "upload by URL" approach? When you integrate with external services that host images, videos and/or documents, and you want to access them (to view or download them),most of the time you are provided with a URL to those assets, not the actual binary object. Then, you can use that URL to access those assets to upload them directly into a workspace.


API /v3/workspaces/<workspaceId>/elements
Method POST
Comments

See the example below to learn how to upload an image by URL.

# If you do not define height and width, 
# the image will be centered in an squared area defined by whatever value is higher: width or height.
# This may lead to think that the image is wrongly positioned, not in the (x,y) position you specified.
curl --location --request POST '/v3/workspaces/<SET_WORKSPACEID>/elements?relativeToOriginOf=<CANVAS_ID>' \
--header 'Authorization: Bearer <SET_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "type": "Image",
    "transform": {
        "x": 50,
        "y": 50
    },
    "sourceUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Yosemite_nat_park_valley_view.JPG/1200px-Yosemite_nat_park_valley_view.JPG",
    "imageFormat": "jpg",
    "title": "Yosemite"
}'

const request = require('request-promise');

const token = '<SET_TOKEN>'
const portal = '';
const workspaceId = '<WORKSPACE_ID>';
const api_version = '/v3';

const request_headers = {
    'Authorization': "Bearer " + token,
    'Content-Type' : 'application/json'    
};

async function main() {
    const zygote_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/elements?relativeToOriginOf=<CANVAS_ID>';
    const zygote_body = {
        "type": "Image",
        "title": "Yosemite",
        "imageFormat": "jpg",
        "transform": { "x": 50, "y": 50, "scale": 1 },
        "sourceUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Yosemite_nat_park_valley_view.JPG/1200px-Yosemite_nat_park_valley_view.JPG"
    }
    const zygote_options = {
        headers: request_headers,
        uri: zygote_endpoint,
        method: 'POST',
        json: zygote_body
    }

    const zygote_response = await request(zygote_options)
    .catch(err => console.log(err))

    answer = JSON.stringify(zygote_response);
    console.log(answer);
}

main();

import requests
import pprint

token = '<SET_TOKEN>'
portal = '';
workspaceId = '<WORKSPACE_ID>';
api_version = '/v3';

request_headers = {
    'Authorization': "Bearer " + token,
    'Content-Type' : 'application/json'    
}

if __name__ == "__main__":
    zygote_endpoint = portal + api_version + '/workspaces/' + workspaceId + '/elements?relativeToOriginOf=<CANVAS_ID>'
    zygote_body = {
        "type": "Image",
        "title": "Yosemite view",
        "imageFormat": "jpg",
        "transform": { "x": 50, "y": 50, "scale": 1 },
        "sourceUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Yosemite_nat_park_valley_view.JPG/1200px-Yosemite_nat_park_valley_view.JPG"
    }

    zygote_request = requests.post(zygote_endpoint, headers = request_headers, json = zygote_body)
    zygote_response = zygote_request.json()
    pprint.pprint(zygote_response)

In the REST APIs, while the image is still uploading, the status is visible in the traits of the elements, as "transfering" in the response body. For example:

{
  "data": {
    "image": {
      "id": "<IMAGE_ID>",
      ...
      "traits": {
        "http://bluescape.dev/zygote/v1/ingestionState": {
          "http://bluescape.dev/zygote/v1/ingestionState/timestamp": 1633481223994,
          "http://bluescape.dev/zygote/v1/ingestionState/stage": "transferring"
        }
      }
      ...
    }
  }
}
Once the upload is completed, the traits will be empty, which means that the image upload is complete and you can access it in the workspace.

To upload video you will use the same API, but slightly different payload. The main changes are the value in type and use a videoFormat field instead of imageFormate. The example below is set to upload a .mov video:

curl --location --request POST '/v3/workspaces/<SET_WORKSPACEID>/elements?relativeToOriginOf=<CANVAS_ID>' \
--header 'Authorization: Bearer <SET_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "type": "Video",
    "transform": {
        "x": 12000,
        "y": 200
    },
    "sourceUrl": "<ADD_URL_TO_MOV_VIDEO>",
    "videoFormat": "mov"
   
}'

Upload by URL using GraphQL APIs

You can use the URL pointing to a resource to upload it into a workspace.
When should we use this "upload by URL" approach? When you integrate with external services that host images, videos and/or documents, and you want to access them (to view or download them),most of the time you are provided with a URL to those assets, not the actual binary object. Then, you can use that URL to access those assets to upload them directly into a workspace.

Please note that <CANVAS_ID> is the ID of the preciously created canvas. This is the canvas where you want to add the uploaded content. The position of the content to upload will be in reference to the top-left corner of this Canvas. You can use the following mutations: createDocument, createImage, createVideo. To specify that you will sue a URL for the upload of the asset, you will need to use the sourceUrl field in the mutation, to specify the URL of the asset to upload.

Example of createImage mutation:

mutation uploadImageFromURL($workspaceId: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $url:String! $scale:Float $idCanvasRelativeTo:String!) {
    createImage(
          workspaceId: $workspaceId, relativeToOriginOf: $idCanvasRelativeTo
          input: {
            title:$title
            imageFormat: $imageFormat
            sourceUrl: $url
            transform: {
              x: $x
              y: $y
              scale:$scale
            }
    })
    #return values after creation:
    {
        content{ uploadId url fields}
        preview{uploadId url fields}
        image{id width height ingestionState}
    }
}

and these are the variables for the mutation above:

{
    "workspaceId": "<workspaceId>",
    "idCanvasRelativeTo": "<CANVAS_ID>",
    "title":"<title_or_filename>",
    "imageFormat":"<SEE_ALLOWED_TYPES_AT_THE_TOP_OF_THE_PAGE>",
    "previewFormat":"<SEE_ALLOWED_TYPES_AT_THE_TOP_OF_THE_PAGE>",
    "url":"<URL_POINTING_TO_THE_IMAGE_TO_UPLOAD>",
    "x":<X_COORDINATE>,
    "y":<Y_COORDINATE>,
    "scale":<SCALE_FACTOR>
}

Example of parameters to load an image:
{
    "workspaceId": "<workspaceID>",
    "idCanvasRelativeTo": "<CANVAS_ID>",
    "title":"Yosemite, open view",
    "imageFormat":"jpeg",
    "previewFormat":"jpeg",
    "url":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Yosemite_nat_park_valley_view.JPG/1200px-Yosemite_nat_park_valley_view.JPG",
    "x":0,
    "y":0,
    "scale":1
}

Here are implementation examples for the upload by URL using GraphQL and the sample image above:

curl --location -g --request POST '/v3/graphql' \
--header 'Authorization: Bearer <SET_TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"mutation uploadImageFromURL($workspaceId: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $url:String! $scale:Float $idCanvasRelativeTo:String!) {\n    createImage(\n          workspaceId: $workspaceId, relativeToOriginOf: $idCanvasRelativeTo\n          input: {\n            title:$title\n            imageFormat: $imageFormat\n            sourceUrl: $url\n            transform: {\n              x: $x\n              y: $y\n              scale:$scale\n            }\n    }) \n    #return values after creation:\n    {\n        content{ uploadId url fields}\n        preview{uploadId url fields}\n        image{id width height ingestionState}\n    }\n}","variables":{"ws":"<WORKSPACE_ID>","idCanvasRelativeTo": "<CANVAS_ID>","title":"Yosemite, grand view","imageFormat":"jpeg","previewFormat":"gif","url":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Yosemite_nat_park_valley_view.JPG/1200px-Yosemite_nat_park_valley_view.JPG","x":0,"y":0,"scale":1}}'    
    

const axios = require('axios');

const url = "/v3/graphql"
const token = "<SET_TOKEN>"
const query =
    `mutation uploadImageFromURL($workspaceId: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $url:String! $scale:Float $idCanvasRelativeTo: String!) {
        createImage(
              workspaceId: $workspaceId, relativeToOriginOf: $idCanvasRelativeTo
              input: {
                title:$title
                imageFormat: $imageFormat
                sourceUrl: $url
                transform: {
                  x: $x
                  y: $y 
                  scale:$scale
                }
        }) 
        #return values after creation:
        {
            content{ uploadId url fields}
            preview{uploadId url fields}
            image{id width height ingestionState}
        }
    }`
const variables = {
    "workspaceId": "<WORKSPACE_ID>",
    "idCanvasRelativeTo": "<CANVAS_ID>",
    "title": "Yosemite, open view",
    "imageFormat": "jpeg",
    "previewFormat": "jpeg",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Yosemite_nat_park_valley_view.JPG/1200px-Yosemite_nat_park_valley_view.JPG",
    "x": 0,
    "y": 0,
    "scale": 1
}
const uploadImageByURL = async () => {
    try {
        const response = await axios.post(url,
            {
                query: query,
                variables: variables
            },
            { headers: { "Authorization": "Bearer " + token } })

        answerData = JSON.stringify(response.data);
        console.log(answerData);
        return response
    }
    catch (error) {
        console.error(error)
    }
}

uploadImageByURL();

import requests
import pprint

'''
Required modules:
   requests 2.22.0
'''

url = "/v3/graphql"
token = "<SET_TOKEN>"
query = """
    mutation uploadImageFromURL($workspaceId: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $url:String! $scale:Float $idCanvasRelativeTo: String!) {
        createImage(
              workspaceId: $workspaceId, relativeToOriginOf: $idCanvasRelativeTo
              input: {
                title:$title
                imageFormat: $imageFormat
                sourceUrl: $url
                transform: {
                  x: $x
                  y: $y 
                  scale:$scale
                }
        }) 
        #return values after creation:
        {
            content{ uploadId url fields}
            preview{uploadId url fields}
            image{id width height ingestionState}
        }
    }
    """

variables = {
    "workspaceId": "<WORKSPACE_ID>",
    "idCanvasRelativeTo": "<CANVAS_ID>",
    "title": "Yosemite, open view",
    "imageFormat": "jpeg",
    "previewFormat": "jpeg",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Yosemite_nat_park_valley_view.JPG/1200px-Yosemite_nat_park_valley_view.JPG",
    "x": 0,
    "y": 0,
    "scale": 1
}

def makeQuery():
    response = requests.post(url,
                             headers={"Authorization": "Bearer " + token
                                      },
                             json={
                                 'query': query,
                                 'variables': variables
                             })
    return response.json()

if __name__ == "__main__":
    response = makeQuery()
    pprint.pprint(response)

The immediate answer is an "ingestionState": "transferring" status, it means that the upload has started:

{
  "data": {
    "createImage": {
      "content": null,
      "preview": null,
      "image": {
        "id": "615c7d676c89c876cd079349",
        "width": 1000,
        "height": 1000,
        "ingestionState": "transferring"
      }
    }
  }
}

Review the upload Ingestion Status using GraphQL APIs

To verify that an image has been successfully uploaded, you can verify the value of the ingestionState field of a particular image, using the image id:

query checkStatusImageIngestion ($workspace: String! $imageId: String!){
    elements(workspaceId:$workspace type:Image id:$imageId) {
    	... on Image {
          id
          __typename
          ingestionState
         }
  }
}

Use these variables:

{
  "workspace": "<WORKSPACE_ID>",
  "imageId" : "<IMAGE_ID>"

}

The response will inform you of the current status of the ingestion of the upload (image, video or document). For example, for an image for which the upload has been completed:

{
  "data": {
    "elements": [
      {
        "id": "<IMAGE_ID>",
        "__typename": "Image",
        "ingestionState": "complete_success"
      }
    ]
  }
}

If you have any questions or comments, please contact us in the Bluescape Community site.