Developer

Upload content into a Canvas for v2

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

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

Images png, jpeg, gif, tiff, pdf, docx, pptx, xlsx, doc, ppt, xls
Documents doc, docx, ppt, pptx, xls, xlsx, pdf, docMime, docxMime, pptMime, pptxMime, xlsMime, xlsxMime, pdfMime
Videos mp4, mov, m4v, quicktime, mp4Mime, m4vMime, quicktimeMime

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 drive or upload by a URL.

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. 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.


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:

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>';
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>'
    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 2: Upload the file to S3

In this step, you will use the S3 credentials generated in the previous step. You will use them to upload the file into 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.

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.

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

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

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)

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)

Upload by URL using REST 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.


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

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

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": 12000,
        "y": 200
    },
    "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"
}'

 /*

How to run:
node this_script_name.js

Requires "axios" module (0.19.0), run:
npm install request-promise
npm install fs

*/

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

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": "Nachooooo.jpg",
        "imageFormat": "jpg",
        "transform": { "x": 100, "y": 100, "scale": 1 },
        "sourceUrl": "https://i.imgur.com/k66EOEe.jpeg"
    }
    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)
}

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": "Nachooooo.jpg",
        "imageFormat": "jpg",
        "transform": { "x": 100, "y": 100, "scale": 1 },
        "sourceUrl": "https://i.imgur.com/k66EOEe.jpeg"
    }

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

To upload video you will use the same API, but slightly different payload. 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_MOVIE>",
    "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.

Example of createImage mutation:

mutation uploadImageFromURL($workspaceId: String! $x:Float! $y:Float! $title:String! $imageFormat:ImageFormatInput! $url:String! $scale:Float) {
    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>",
    "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
}

If you have any questions or comments, please contact us at Bluescape support.