Programmatically Load Images¶
Introduction¶
This tutorial outlines how you can load images onto supported entities in VibeIQ using Contrail SDK or cURL.
Covered Concepts
- Uploading files with Contrail SDK or cURL.
- Linking images to Items using Contrail SDK or cURL.
- Linking images to Colors using Contrail SDK or cURL.
End Product of this Tutorial
- Images will be uploaded as File entities.
- Images will be assigned to Items or Colors via Content and will be visible on their respective entity views in VibeIQ applications.
Authentication¶
App Authentication
If your image load is running as part of an app action, you do not need to explicitly authenticate, and can skip this section. App actions are authorized automatically during runtime.
For detailed information on authenticating with Contrail, including how to generate API keys and use them with the SDK, see the Authentication & API Access guide.
Step 1: Create Image Files¶
Images can be uploaded as File entities to the VibeIQ platform using either the SDK or direct API calls.
The SDK provides the Files#createAndUploadFileFromBuffer method for uploading images.
This example assumes there is a subdirectory called images in the current directory where all the image files are located. The contentType is set to image/jpeg.
import {login, Files, Entities} from '@contrail/sdk';
import * as fs from "fs/promises"
import * as path from "node:path";
const pLimit = require('p-limit');
const limit = pLimit(10);
const loginCredentials = {
orgSlug: process.env.USER_ORG,
apiKey: process.env.API_KEY,
};
async function loadImages() {
await login(loginCredentials);
let fileUploadOperations = [];
let contentToCreate = [];
const contentType = "image/jpeg";
const imagesToUpload = await fs.readdir('./images');
for (const fileName of imagesToUpload) {
const promise = limit(async () => {
try {
const arrayBuffer = await fs.readFile(`./images/${fileName}`);
const imageBuffer = Buffer.from(new Uint8Array(arrayBuffer));
const createdFile = await new Files().createAndUploadFileFromBuffer(
imageBuffer,
contentType,
fileName,
null,
24 * 60 * 60 * 1000
);
contentToCreate.push({
copyFileId: createdFile.id,
contentType,
fileName,
})
} catch (e) {
console.log(
`Failed to upload image`,
e
);
}
});
fileUploadOperations.push(promise);
}
await Promise.all(fileUploadOperations);
}
Uploading images via cURL is a two-step process:
Step 1: Request Pre-Signed Information
First, request pre-signed information from the VibeIQ API to get temporary access to upload the image securely to an S3 bucket.
curl -X POST "https://api.vibeiq.com/prod/api/files" \
-H "x-api-key: $API_KEY" \
-H "x-api-org: $USER_ORG" \
-H "Content-Type: application/json" \
-d '{
"contentType": "image/jpeg",
"fileName": "image.jpeg",
"ttl": 86400
}'
The API returns a response containing:
- File metadata (including
fileKeyandid) - A pre-signed S3 URL and upload form information (
uploadPost) - AWS authorization fields
{
"contentType": "image/jpeg",
"fileName": "image.jpeg",
"ttl": 86400,
"fileKey": "0FYWOhu9p50/some-file-key/775d-8284-4c8d-b454-4509f9ebe92e",
"fileBucket": "bucket-name",
"fileUrl": "https://api.vibeiq.com/prod/api/files/downloadUrl/0FYWOhu9NMilc8d-b454-4509f9ebe92e",
"createdOn": "2025-03-17T21:43:18.511Z",
"updatedOn": "2025-03-17T21:43:18.511Z",
"createdById": "9-4sss39qP",
"updatedById": "9-4sunF4439qP",
"orgId": "0FYWOhilep50",
"id": "fkS4cOA25o5IX",
"uploadPost": {
"url": "https://s3.amazonaws.com/prod-contrail-file-content-bucket",
"fields": {
"key": "0FYWOhu9p50/some-file-key/775d-8284-4c8d-b454-4509f9ebe92e",
"bucket": "prod-contrail-file-content-bucket",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "A0000000/17000/us-east-1/s3/aws4_request",
"X-Amz-Date": "20250317T214318Z",
"X-Amz-Security-Token": "IQoJbAIncgQjVi3some-security-code6G2dGuFFO/XolLglSgc+jNb0W4sF7rGvcWauKvqByjx4ob5yIAE4qAO7mSawTHKwIyy1Q==",
"Policy": "eyJleHBpcmF0aW9uIjoiMjAyNS0wMy0xN1Lsome-policy-codeSxbImVxIiwiJQiLCIwiaW1hZ2UvanBTzdtU2F3VEhLd0l5eTFRPT0ifV19",
"X-Amz-Signature": "b1c224f08a25d94f3387b908some-signature-68ce560c50fa65676c1b497401db3b",
"x-amz-meta-fileName": "image.jpeg",
"x-amz-meta-uploaderId": "9-4NZnF4439qP",
"x-amz-meta-orgId": "0FYWOhilep50",
"x-amz-meta-contentType": "image/jpeg",
"x-amz-meta-fileId": "fkS4c25o5IX"
}
},
"downloadUrl": "https://prod-contrail-file-content-bucket.s3.amazonaws.com/0FYWOh9Milep50de10d-8284-4c8d-b454-4509f9ebe92e?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAXGPNJ5%2F20250317%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250317T214318Z&X-Amz-Expires=86400&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEPb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQCYBp%2FRbTHTUUrpWBy9c3tqpfuLcnhIv2ONaNzsiaOfMgIhAIncgQjVi36G2dGz2U9EfHfGaFCoZulRa3Rz3Q8iKiF3KpMDCE8QAxoMNDk0OTU1NzgyMDA2Igy5rGbRF9IWTdl7KTkq8AKDSBmbz7bRyonZiiQuROgPuFFO%2FXolLg%2Bm%2FDQ%2FOa1a3jn6pUp2Ij0jigzC417R7y%2B9oITWQ3Ixe%2BLwzY4C5mob2kyW%2FUDB3PaO9T306xvT2wPSgc%2BjNb0W4sF7rGvcWKtHCctPJ6TR4899GKDUnmgfTpIbokDC1ypARaAMeBAuId1q7%2Fm298Qi2WPGYhur985IigzEtJ16pVeMGYCSfz1GF8lvfTYLxZFISf52aqglXAQDtKSo0A24bOi%2F96Zm8m8GSIB75oUYaWkrErt3aI8ieu8i%2BJzWEy2K7s9VqxCYuAyxOWW4muhVMhYTVKhvjZpA%2FcTGZDb66mtJjPuKOmfrNpml8JTPpmL7oSA3FVE3v7a423l0EMjrt5DFXeAuvHE7iGYWjws5u%2B3QwVQQ0PuTKUTm7E6eL8TTcCumqee4rrP7sEhiVbpXKvn9jLI8tTtjWCtwWeDMFvLFUaYfabJCdfwxyBSztxfqi7rSN9o5vzCxquK%2BBjqcAZasSaGGDmjjAQF%2B%2BpHdIxzkZowSSAWAt6qN7VA7e9%2BPHztDDgrSZnyM0gCpoTZWK7DcWCxQCCpC6MNlXmGsUIHmNM4JFm%2F9xBsim%2BUkYLTAWC23mZkk7xpN%2FtMiyF1C%2BwtcDlWB79kAFU7zsPG32PKcU8znJ1jP7zYzOT0ofG6eMAPauKvqByjx4ob5yIAE4qAO7mSawTHKwIyy1Q%3D%3D&X-Amz-Signature=9aa9bc64b8c34e47aecd1ba2588f9a3cd912d63da120de03be2e7a2e439e6967&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3Dimage.jpeg",
"adminDownloadUrl": "https://admin.vibeiq.com/org/your-org-name/files/fkS4o5IX/download"
}
Step 2: Upload the File to S3
Using the uploadPost data from the previous step, upload the file to S3.
curl -X POST 'https://s3.amazonaws.com/prod-contrail-file-content-bucket' \
-H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundary123456' \
-F 'key=0FYWOhu9p50/some-file-key/775d-8284-4c8d-b454-4509f9ebe92e' \
-F 'bucket=prod-contrail-file-content-bucket' \
-F 'x-amz-meta-contentType=image/jpeg' \
-F 'Content-Type=image/jpeg' \
-F 'X-Amz-Algorithm=AWS4-HMAC-SHA256' \
-F 'X-Amz-Credential=A0000000/17000/us-east-1/s3/aws4_request' \
-F 'X-Amz-Date=20250317T214318Z' \
-F 'X-Amz-Security-Token=IQoJbAIncgQjVi3some-security-code6G2dGuFFO/XolLglSgc+jNb0W4sF7rGvcWauKvqByjx4ob5yIAE4qAO7mSawTHKwIyy1Q==' \
-F 'Policy=eyJleHBpcmF0aW9uIjoiMjAyNS0wMy0xN1Lsome-policy-codeSxbImVxIiwiJQiLCIwiaW1hZ2UvanBTzdtU2F3VEhLd0l5eTFRPT0ifV19' \
-F 'X-Amz-Signature=b1c224f08a25d94f3387b908some-signature-68ce560c50fa65676c1b497401db3b' \
-F 'x-amz-meta-fileName=image.jpeg' \
-F 'x-amz-meta-uploaderId=9-4NZnF4439qP' \
-F 'x-amz-meta-orgId=0FYWOhilep50' \
-F 'x-amz-meta-fileId=fkS4c25o5IX' \
-F 'file=@/path/to/image.jpeg'
Step 2: Create Content to Link Images to Items or Colors¶
You can link images to items or colors by creating a Content entity. The Content entity acts as a link between a File and an Item or Color.
To create a Content entity, you need to provide the following data:
contentHolderReference: A reference to the entity the image will be linked to, in the formatitem:<item ID>orcolor:<color ID>.contentType: The content type of the image being uploaded (e.g.,image/jpeg).copyFileId: The ID of the file you created in the previous step.
const tasks = contentToCreate.map((content) =>
limit(async () => {
try {
const itemFederatedId = content.fileName.split("_")[0];
// Fetch the item based on its federated ID
const item = await new Entities().get({
entityName: 'item',
federatedId: itemFederatedId,
});
// Create the content linked to the item
const resultingContent = await new Entities().create({
entityName: 'content',
object: {
contentHolderReference: `item:${item.id}`,
contentType,
copyFileId: content.copyFileId,
},
});
} catch (e) {
console.log(`Failed to create content for ${content.fileName}`, e);
}
})
);
// Wait for all the mapped tasks to finish
await Promise.all(tasks);
}
This will successfully attach an image to each of the Items provided the Items federated IDs are indicated in the file names.
const tasks = contentToCreate.map((content) =>
limit(async () => {
try {
const colorFederatedId = content.fileName.split("_")[0];
// Fetch the color based on its federated ID
const color = await new Entities().get({
entityName: 'color',
federatedId: colorFederatedId,
});
// Create the content linked to the color
const resultingContent = await new Entities().create({
entityName: 'content',
object: {
contentHolderReference: `color:${color.id}`,
contentType,
copyFileId: content.copyFileId,
},
});
} catch (e) {
console.log(`Failed to create content for ${content.fileName}`, e);
}
})
);
// Wait for all the mapped tasks to finish
await Promise.all(tasks);
}
This will successfully attach an image to each of the Colors provided the Colors federated IDs are indicated in the file names.
curl -X POST '{{baseURL}}/content' \
-H 'x-api-key: $API_TOKEN' \
-H 'x-api-org: $USER_ORG' \
-H 'Content-Type: application/json' \
-d '{
"contentHolderReference": "item:mkzvjUlEFzKv-jUc",
"contentType": "image/jpeg",
"copyFileId": "jhQntcifRfjQkQ0G"
}'
Some iterative method will be required to run this request automatically for all items and images.
curl -X POST '{{baseURL}}/content' \
-H 'x-api-key: $API_TOKEN' \
-H 'x-api-org: $USER_ORG' \
-H 'Content-Type: application/json' \
-d '{
"contentHolderReference": "color:mkzvjUlEFzKv-jUc",
"contentType": "image/jpeg",
"copyFileId": "jhQntcifRfjQkQ0G"
}'
Some iterative method will be required to run this request automatically for all colors and images.