---
title: "Rendering through the Personalization Platform | Customer&#x27;s Canvas Hub"
canonical: "https://customerscanvas.com/docs/hub/dev-center/api/storefront/rendering-print-files/"
---

# Rendering through the Personalization Platform

> This article describes using the _Storefront Projects API_. You can render private designs from editors and convert them to PDFs or other print files via the cloud rendering service of the Personalization Platform. The rendering service provides you with a robust queue manager for handling output PDFs and serving them over FTP or invoking by URL.

## Assumptions

Assumptions used in this guide:

- You have access to a Customer's Canvas account.
- You have a [registered storefront](https://customerscanvas.com/docs/hub/dev-center/api/storefront/register-storefront.md).
- You have [registered users](https://customerscanvas.com/docs/hub/dev-center/api/storefront/register-customers.md).
- You have created a page with [an editor](https://customerscanvas.com/docs/hub/dev-center/frontend-integration/about-design-editor.md), which returns state files.
- You have learned how authentication and API clients work in Customer's Canvas:
    
    - [Official API clients](https://customerscanvas.com/docs/hub/dev-center/api.md)
    - [Example of using the C# API Client](https://customerscanvas.com/docs/hub/dev-center/api/clients/cs-api-client.md)

## Rendering print-ready files

Let's suppose you have received an output from the Handy Editor or the Design Editor, which contains, among other things, a private design ID.

To render print-ready files, you need to create a _project_ that runs a _rendering pipeline_ and stores private designs, outputs, data obtained during personalization, and order details.

There are three approaches to using pipelines:

- [**Built-in pipeline**](#pdf-rendering-in-cmyk-at-300-dpi)

  When you only need to get a print file for a design, you can create a project with a built-in pipeline through the endpoint [/api/storefront/v1/projects/by-scenario/render-hires](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md).

- [**Custom pipeline**](#custom-pipelines-with-invoke-url)

  Another approach is preparing a rendering pipeline. Use this approach when you need not only to render a design but also perform post-processing, like sending a file or making additional operations. [Create a pipeline](#custom-pipelines-with-invoke-url) and use it in the endpoint [`POST /api/storefront/v1/projects/by-scenario/specific-pipeline`](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md#tag/projects/CreateBySpecificPipelineScenario).

- [**PIM product pipeline**](#pipelines-of-pim-products)

  When using the PIM module, you can use the pipeline linked to the PIM product. To identify the pipeline, you will need to pass a storefront product reference. To create a project, call the endpoint [/api/storefront/v1/projects/with-single-item](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md#tag/projects/CreateWithSingleItem).
  
Projects start the rendering process immediately after they are created. Since this is an asynchronous process, you need to poll the project status to get artifacts (i.e., print files), as explained in the [Downloading artifacts](https://customerscanvas.com/docs/hub/dev-center/scenarios/online-store/receiving-print-ready-files.md) article.

As an alternative to polling the project, you can create a pipeline with an [`invoke-url`](https://customerscanvas.com/docs/hub/printer-guide/pipelines/tasks/invoke-url.md) task to send the resulting artifacts to the specified URL once the job is complete.

To learn how to create PIM products, refer to the [Creating Products Tutorial](https://customerscanvas.com/docs/hub/admin-guide/pim/creating-products.md#creating-a-new-product).

## Examples

### PDF rendering in CMYK at 300 DPI

Generating high-resolution files does not always require explicitly creating rendering pipelines.

In the simplest case, when you only need to get a print file for a design, you can create a project with a built-in pipeline that performs a single [render-hires](https://customerscanvas.com/docs/hub/printer-guide/pipelines/tasks/render-hires.md) task. To do so, use the endpoint [/api/storefront/v1/projects/by-scenario/render-hires](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md).

In the following example, you may learn how to create a project and then extract the print file. Let's render a design with ID `62da200abb25c5477797d9cb` linked to storefront `12` to the file **print.pdf**.

**cURL**

```bash
# First, send this request.

curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "name": "print"
    }
  }'

# Then, poll the project's status to see if it is completed or failed.
# For example, if the project ID is 4657, send this request
# and wait for the Completed or Failed status. 

curl -X \
  GET "https://api.customerscanvashub.com/api/storefront/v1/projects/4567/processing-results" \
  -H  "accept: text/plain" \
  -H  "Authorization: Bearer <TOKEN>"
```

**HTTP**

```bash
# First, send this request.

POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12
Content-Type: application/json
{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "name": "print"
    }
}

# Then, poll the project's status to see if it is completed or failed.
# For example, if the project ID is 4657, send this request
# and wait for the Completed or Failed status. 

GET https://api.customerscanvashub.com/api/storefront/v1/projects/4567/processing-results
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, a design ID, and a file name.
var body = """
{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "name": "print"
    }
}
""";
var project = await projectsApiClient.CreateByRenderHiResScenarioAsync(storefrontId, null, body);
// Poll the project status.
do
{
    // Wait for 15 seconds.
    await Task.Delay(TimeSpan.FromSeconds(15));
    // Get the project status.
    var result = await projectsApiClient.GetProjectProcessingResultsAsync(project.id);
    // Get the print file URL or show the exception.
    if (result.Status == ProjectProcessingStatus.Completed)
    {
        return result.OutputFileDetails.First().Url;
    } 
    else if (result.Status == ProjectProcessingStatus.Failed)
    {
        throw new Exception("The rendering job failed");
    }
} while (true);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, a design ID, and a file name.
var body = {
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "name": "print"
    }
};
var project = _projectsApiClient.createByRenderHiResScenario(storefrontId, null, JSON.stringify(body));
// Poll the project status.
await pollProject(project.id);
async function pollProject(projectId: string): Promise<string> {
    while (true) {
        // Wait for 15 seconds.
        await delay(15000);
        // Get the project status.
        const result = _projectsApiClient.getProjectProcessingResults(projectId);
        // Get the print file URL or show the exception.
        if (result.status === ProjectProcessingStatus.Completed) {
            return result.outputFileDetails[0].url;
        } else if (result.status === ProjectProcessingStatus.Failed) {
            throw new Error("The rendering job failed");
        }
    }
}
```

**PHP**

```php
// Set a real storefront ID.
$storefrontId = 12;
// Set an owner ID, a design ID, and a file name.
$body = "{'ownerId': '6527afc635b52bfadd355226', 'scenario': { 'designId': '62da200abb25c5477797d9cb', 'name': 'print'}}";
$project = $projectsApiClient->projectsCreateByRenderHiResScenario($storefrontId, null, $body);
// Poll the project status.
do {
    // Wait for 15 seconds.
    sleep(15);
    // Get the project status.
    $result = $projectsApiClient->projectsGetProjectProcessingResults($project->id);
    // Get the print file URL or show the exception.
    if ($result->status === ProjectProcessingStatus::COMPLETED) {
        return $result->outputFileDetails[0]->url;
    } elseif ($result->status === ProjectProcessingStatus::FAILED) {
        throw new Exception("The rendering job failed");
    }
} while (true);
```

Depending on the design size, the rendering process may take some time. Therefore, you will not get the result immediately, but will need to monitor the progress of the project. To poll the project's status, use the project ID provided in the response. The status can be obtained through the endpoint [`/api/storefront/v1/projects/{id}/processing-results`](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md) as described in the [Downloading artifacts](https://customerscanvas.com/docs/hub/dev-center/scenarios/online-store/receiving-print-ready-files.md) article. Keep polling until the status becomes `"Completed"`.

The mandatory parameters for this endpoint are the storefront ID, the design ID, and ID of this design owner. So, you can pass this minimal set of parameters as follows:

**cURL**

```bash
curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": { "designId": "62da200abb25c5477797d9cb"}
  }'
```

**HTTP**

```bash
POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12
Content-Type: application/json
{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": { "designId": "62da200abb25c5477797d9cb"}
}
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID and a design ID.
var body = """
{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": { "designId": "62da200abb25c5477797d9cb"}
}
""";
var project = await projectsApiClient.CreateByRenderHiResScenarioAsync(storefrontId, null, body);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID and a design ID.
var body = {
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": { "designId": "62da200abb25c5477797d9cb"}
};
var project = _projectsApiClient.createByRenderHiResScenario(storefrontId, null, JSON.stringify(body));
```

**PHP**

```php
// Set a real storefront ID.
$storefrontId = 12;
// Set an owner ID and a design ID.
$body = "{'ownerId': '6527afc635b52bfadd355226', 'scenario': { 'designId': '62da200abb25c5477797d9cb'}}";
$project = $projectsApiClient->projectsCreateByRenderHiResScenario($storefrontId, null, $body);
```

If you need to track the result in your Customer's Canvas account, specify the project's `ownerId` the same as the design's `ownerId`.

In the request body, you can pass the `scenario` object with the following rendering parameters:

- `designId` is an identifier of a private design.
- `name` is an output file name without the extension, `"result"` by default.
- `dpi` is output file DPI, `300` by default.
- `format` is the output file format, `"Pdf"` by default.
- `colorSpace` is the output file color space, `"Cmyk"` by default.
- `flipMode` is the flip mode of the output file, `"None"` by default.
- `anonymousAccess` is the mode of accessing the output file, `false` by default.

The access mode defines how you can download the output files. Depending on your purpose, you can use them within your application or send them to a third-party system. Therefore, you may apply authentication or not when downloading files from the resulting link. This is how you can grant anonymous access to print files via a link.

**cURL**

```bash
curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "anonymousAccess": true
    }
  }'
```

**HTTP**

```bash
POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12
Content-Type: application/json
{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "anonymousAccess": true
    }
}
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, a design ID, and an anonymous access flag.
var body = """
{
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "anonymousAccess": true
    }
}
""";
var project = await projectsApiClient.CreateByRenderHiResScenarioAsync(storefrontId, null, body);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, a design ID, and an anonymous access flag.
var body = {
    "ownerId": "6527afc635b52bfadd355226",
    "scenario": {
       "designId": "62da200abb25c5477797d9cb",
       "anonymousAccess": true
    }
};
var project = _projectsApiClient.createByRenderHiResScenario(storefrontId, null, JSON.stringify(body));
```

**PHP**

```php
// Set a real storefront ID.
$storefrontId = 12;
// Set an owner ID, a design ID, and an anonymous access flag.
$body = "{'ownerId': '6527afc635b52bfadd355226', 'scenario': { 'designId': '62da200abb25c5477797d9cb', 'anonymousAccess': true}}";
$project = $projectsApiClient->projectsCreateByRenderHiResScenario($storefrontId, null, $body);
```

### Custom pipelines with invoke-url

The [built-in pipeline](#pdf-rendering-in-cmyk-at-300-dpi) may not be enough for you. In this case, you can create a pipeline in the Customer's Canvas admin panel. You can find the details on how to do this in the [Pipelines Tutorial](https://customerscanvas.com/docs/hub/printer-guide/pipelines.md).

For our purpose, a pipeline may consist of the following steps:

- `extract-project-design` - adds a design file to a pipeline.
- `render-hires` - renders a PDF artifact at a resolution of 200 DPI.
- `configure-access` - makes it possible to download a PDF anonymously.
- `invoke-url` - transfers files to an external address.
- `finalize` - marks the specified artifacts as final and removes all temporary data.

```json
{
    "tasks": [
        {
            "description": "Extract design from project",
            "name": "extract-design",
            "type": "extract-project-design",
            "parameters": {},
            "outputArtifacts": [
                "design"
            ]
        },
        {
            "description": "Render design",
            "name": "render-print-file",
            "type": "render-hires",
            "inputArtifacts": [
                "design"
            ],
            "parameters": {
                "hiResOutputDpi": 200
            },
            "outputArtifacts": [
                "result"
            ]
        },
        {
            "description": "Configure print file access",
            "name": "configure-access",
            "type": "configure-artifacts",
            "inputArtifacts": [
                "result"
            ],
            "parameters": {
            "anonymousAccess": "true"
            }
        },
        {
            "description": "Print file delivery to cloud storage",
            "name": "delivery",
            "type": "invoke-url",
            "inputArtifacts": [
                "result"
            ],
            "parameters": {
                "url": "https://example.com/savefiles",
                "httpMethod": "POST",
                "queryHeaders": {
                    "x-api-key": "abc123"
                },
                "body": {
                    "projectId": "{{project.id}}",
                    "orderId": "{{project.orderId}}",
                    "url": "{{result:url}}"
                }
            }
        },
        {
            "description": "Finalize",
            "name": "finalizing",
            "type": "finalize",
            "finalArtifacts": [
                "result"
            ]
        }
    ]
}
```

To deliver the project results, you need to implement a simple web interface that accepts a request from the pipeline and specify its URL in the `invoke-url` task. After that, it's enough to just create a project. When the pipeline has finished, you will receive a request on the web interface that you have specified. In this request, you will pick up the rendered artifacts.

You can open the **Projects** section in your Customer's Canvas account and control how the pipeline is created and executed, which may be important when you are just setting it up.

To create a project, use the endpoint [`POST /api/storefront/v1/projects/by-scenario/specific-pipeline`](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md#tag/projects/CreateBySpecificPipelineScenario). In the request body, pass the `ownerId`, `orderId`, `items.designIds`, and the ID of your processing pipeline in `scenario.pipelineId`.

Let's render a design with ID `62da200abb25c5477797d9cb` by using pipeline `247`.

**cURL**

```bash
curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "designIds": ["62da200abb25c5477797d9cb"]
    }],
    "scenario": { "pipelineId": 247 }
  }'
```

**HTTP**

```bash
POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12
Content-Type: application/json
{
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "designIds": ["62da200abb25c5477797d9cb"]
    }],
    "scenario": { "pipelineId": 247 }
}
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, design IDs, and a pipeline ID.
var body = """
{
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "designIds": ["62da200abb25c5477797d9cb"]
    }],
    "scenario": {"pipelineId": 247}
}
""";
var project = await projectsApiClient.CreateBySpecificPipelineScenarioAsync(storefrontId, null, body);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, design IDs, and a pipeline ID.
var body = {
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "designIds": ["62da200abb25c5477797d9cb"]
    }],
    "scenario": {"pipelineId": 247}
};
var project = _projectsApiClient.createBySpecificPipelineScenario(storefrontId, null, JSON.stringify(body));
```

**PHP**

Not available yet.

Instead of using webhooks in `invoke-url`, you may want to add the [upload-to-ftp](https://customerscanvas.com/docs/hub/printer-guide/pipelines/tasks/upload-to-ftp.md) or [send-email](https://customerscanvas.com/docs/hub/printer-guide/pipelines/tasks/send-email.md) tasks to provide the results. For a better understanding of pipelines and for a full specification of the pipeline format, refer to the [Pipelines Intro](https://customerscanvas.com/docs/hub/printer-guide/pipelines.md) documentation.

To learn how to deliver results to your system and process many designs within a request, refer to the [Working with Projects](https://customerscanvas.com/docs/hub/dev-center/api/storefront/working-with-projects.md) topic.

### Pipelines of PIM products

If you have created a PIM product and need to render its designs, you can use the pipeline linked to that product by product reference.

You can define a processing pipeline when editing or creating a PIM product. In your Customer's Canvas account, open **Projects**, click **Edit** or **Create new**, click the **Processing pipeline** drop-down box, and select a pipeline. Note that the pipeline must already be created in your account.

![Defining a processing pipeline.](images/product-pipeline.png)

For more details about creating PIM products, refer to the [Creating PIM products](https://customerscanvas.com/docs/hub/admin-guide/pim/creating-products.md#creating-a-new-product) topic. For more details about crating pipelines, refer to the [Pipelines Tutorial](https://customerscanvas.com/docs/hub/printer-guide/pipelines.md).

To create a project, call the endpoint [/api/storefront/v1/projects/with-single-item](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md#tag/projects/CreateWithSingleItem) and pass a model there. For example, the model to render the design `'62da200abb25c5477797d9cb'` by using a pipeline specified for the product `'24297'` may look as follows:

```json
{
    "ownerId": "6527afc635b52bfadd355226",
    "item": {
        "productReference": "24297",
        "designIds": ["62da200abb25c5477797d9cb"]
    }
}
```

The request will look as follows:

**cURL**

```bash
curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/with-single-item?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "ownerId": "6527afc635b52bfadd355226",
    "item": {
        "productReference": "24297",
        "designIds": ["62da200abb25c5477797d9cb"]
    }
  }'
```

**HTTP**

```bash
POST https://api.customerscanvashub.com/api/storefront/v1/projects/with-single-item?storefrontId=12
{
    "ownerId": "6527afc635b52bfadd355226",
    "item": {
        "productReference": "24297",
        "designIds": ["62da200abb25c5477797d9cb"]
    }
}
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set a product reference, owner ID, and design IDs.
var body = """
{
    "ownerId": "6527afc635b52bfadd355226",
    "item": {
        "productReference": "24297",
        "designIds": ["62da200abb25c5477797d9cb"]
    }
}
""";
var project = await projectsApiClient.CreateWithSingleItemAsync(storefrontId, null, body);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set a product reference, owner ID, and design IDs.
var body = {
    "ownerId": "6527afc635b52bfadd355226",
    "item": {
        "productReference": "24297",
        "designIds": ["62da200abb25c5477797d9cb"]
    }
};
var project = _projectsApiClient.createWithSingleItem(storefrontId, null, JSON.stringify(body));
```

**PHP**

Not available yet.

Note that you need to poll the project's status to see if it is completed or failed as described in the [previous example](#pdf-rendering-in-cmyk-at-300-dpi).

### Rendering multiple line items

You can render multiple products in a project. These can be products the user has added to a cart, and you can process the entire order in one project. You can use the endpoint [/api/storefront/v1/projects/with-multiple-items](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md) to create such a project and link pipelines by product reference.

Let's create a project to render two products. The first product has one design `'642e793dc46d098d64514607'`, while another has two designs `'642d0e469793dc8d64460751'` and `'62d777a20cbbb25c5497d0a9'` to render. Their owner ID is `'6527afc635b52bfadd355226'`. To link pipelines, we pass their product references `'24298'` and `'24299'`.

**cURL**

```bash
curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/with-multiple-items?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "orderLineItemIndex": 0,
        "productReference": "24298",
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "productReference": "24299",
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }]
  }'
```

**HTTP**

```bash
POST https://api.customerscanvashub.com/api/storefront/v1/projects/with-multiple-items?storefrontId=12
Content-Type: application/json
{
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "orderLineItemIndex": 0,
        "productReference": "24298",
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "productReference": "24299",
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }]
}
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an order ID, an owner ID, and a product reference and design IDs for each line item.
var body = """
{
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "orderLineItemIndex": 0,
        "productReference": "24298",
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "productReference": "24299",
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }]
}
""";
var project = await projectsApiClient.CreateWithMultipleItemsAsync(storefrontId, null, body);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an order ID, an owner ID, and a product reference and design IDs for each line item.
var body = {
    "orderDetails": {"orderId": "1004"},
    "ownerId": "6527afc635b52bfadd355226",
    "items": [{
        "orderLineItemIndex": 0,
        "productReference": "24298",
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "productReference": "24299",
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }]
};
var project = _projectsApiClient.createWithMultipleItems(storefrontId, null, JSON.stringify(body));
```

**PHP**

Not available yet.

### Post-processing results

When processing a project that includes many line items of an order, you may want to do some operations only when processing has been completed for all the line items. For example, if you need to send files to your system in a single request only when all of them are rendered, or switch the status in your system at the end.
 
To do this, you can create a separate pipeline and configure it so that it runs at the end of the processing process for a project. Such a post-processing pipeline is global for a single integration.

For example, a post-processing pipeline may consist of only two tasks, `send-email` and `finalize` to send email notifications when rendering is completed.

```json
{
    "tasks": [
        {
            "description": "Email notification",
            "name": "send-email",
            "type": "send-email",
            "parameters": {
                "recipients": "recipient@example.com",
                "subject": "Your print files are ready",
                "body": "You can download print files for your personalized designs here: <br> {{$docs}} <br>",
                "useHtmlBody": true,
                "$docs": "results for item {{project.items.$.name}}: {{$:result*}} <br>"
            },
            "outputArtifacts": []
        },
        {
            "description": "Finalize",
            "name": "finalize",
            "type": "finalize",
            "finalArtifacts": [
                "result*"
            ]
        }
    ]
}
```

You can create a post-processing pipeline in your Customer's Canvas account. To assign it to a single integration, navigate to **Settings** > **Integrations**, hover over the ellipsis in the integration row, click **Edit**, and select a pipeline in the **Project post-processing pipeline** drop-down list.

![Selecting post-processing pipelines.](images/integration-settings.png)

For more details, refer to the [Integration Settings](https://customerscanvas.com/docs/hub/admin-guide/settings/integrations.md) topic.

As you have already learned about rendering a specific pipeline, the endpoint [/api/storefront/v1/projects/by-scenario/specific-pipeline](#custom-pipelines-with-invoke-url) allows you to create a project to process several products defined in the `items` array. This is how you can render the products from the previous example through the custom pipeline `247`.

**cURL**

```bash
curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {"pipelineId": 247}
  }'
```

**HTTP**

```bash
POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12
Content-Type: application/json
{
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {"pipelineId": 247}
}
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an order ID, an owner ID, and design IDs for each line item.
var body = """
{
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {"pipelineId": 247}
}
""";
var project = await projectsApiClient.CreateBySpecificPipelineScenarioAsync(storefrontId, null, body);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an order ID, an owner ID, and design IDs for each line item.
var body = {
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {"pipelineId": 247}
};
var project = _projectsApiClient.createBySpecificPipelineScenario(storefrontId, null, JSON.stringify(body));
```

**PHP**

Not available yet.

In the `scenario` object, you can also apply post-processing in the `postProcessingPipelineId` property. If you need to use different pipelines to process different products, you can assign the pipelines by product names in the `itemPipelines` array as follows:

**cURL**

```bash
curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "name": "product1",
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "name": "product2",
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {
        "postProcessingPipelineId": 11,
        "itemPipelines": [
            { "itemName": "product1", "pipelineId": 247 },
            { "itemName": "product2", "pipelineId": 248 }
        ]
    }
  }'
```

**HTTP**

```bash
POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12
Content-Type: application/json
{
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "name": "product1",
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "name": "product2",
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {
        "postProcessingPipelineId": 11,
        "itemPipelines": [
            { "itemName": "product1", "pipelineId": 247 },
            { "itemName": "product2", "pipelineId": 248 }
        ]
    }
}
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an order ID, an owner ID, design IDs for each line item, and pipelines for processing items.
var body = """
{
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "name": "product1",
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "name": "product2",
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {
        "postProcessingPipelineId": 11,
        "itemPipelines": [
            { "itemName": "product1", "pipelineId": 247 },
            { "itemName": "product2", "pipelineId": 248 }
        ]
    }
}
""";
var project = await projectsApiClient.CreateBySpecificPipelineScenarioAsync(storefrontId, null, body);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an order ID, an owner ID, design IDs for each line item, and pipelines for processing items.
var body = {
    "ownerId": "6527afc635b52bfadd355226",
    "orderDetails": {"orderId": "1004"},
    "items": [{
        "name": "product1",
        "orderLineItemIndex": 0,
        "designIds": ["642e793dc46d098d64514607"]
    },
    {
        "name": "product2",
        "orderLineItemIndex": 1,
        "designIds": ["642d0e469793dc8d64460751", "62d777a20cbbb25c5497d0a9"]
    }],
    "scenario": {
        "postProcessingPipelineId": 11,
        "itemPipelines": [
            { "itemName": "product1", "pipelineId": 247 },
            { "itemName": "product2", "pipelineId": 248 }
        ]
    }
};
var project = _projectsApiClient.createBySpecificPipelineScenario(storefrontId, null, JSON.stringify(body));
```

**PHP**

Not available yet.

## Migration from legacy rendering methods

If you have integrated your system with Customer's Canvas earlier than 2023, you may find that you are using deprecated methods of rendering. They include receiving a PDF link directly from Design Editor or receiving a link through a deprecated endpoint of project.

In this section, you may learn how to migrate from legacy methods to the rendering service.

Let's assume you have obtained a description that contains a private design and consider how to proceed in both cases.

### Old Code

When you sent requests directly to the Design Editor, your code could have looked like this:

```cs
  var ccUrl = $"https://cc-apps.aurigma.net/design-editor/{version}";
  var apiUrl = $"{ccUrl}/api/hires/generateHires";

  var result = new List<string>();
  var model = new GetModel(userId, stateId);

  using (var client = new HttpClient())
  {
      var request = new HttpRequestMessage
      {
          Method = new HttpMethod("POST"),
          RequestUri = new Uri(apiUrl)
      };

      request.Headers.Add(ApiKeyHeaderName, deApiKey);
      request.Content = new StringContent(JsonSerializer.Serialize(model,, Encoding.UTF8, "application/json");

      var response = await client.SendAsync(request);
      var strResponse = await response.Content.ReadAsStringAsync();
      var responseModel = JsonSerializer.Deserialize<List<string>>(strResponse);

      if (responseModel[0] != null)
      {
          result.AddRange(responseModel[0]);
      }

      return result;
  };
```

:::important
This method is no longer recommended and will be removed. Instead, render print files through the [Personalization Platform](#rendering-print-ready-files).
:::

In this code, you need to use `stateId` as the `designId`, and `userId` as  `ownerId` in the project creation endpoint calls. For example, if you need the easiest way to render a design, you can create a project using the [/api/storefront/v1/projects/by-scenario/render-hires](#rendering-through-the-personalization-platform) endpoint as follows:

**cURL**

```bash
# First, send this request. 

curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
       "ownerId": userId,
       "scenario": { "designId": stateId, "name": "print"}
      }'

# Then, poll the project's status to see if it is completed or failed.

curl -X \
  GET "https://api.customerscanvashub.com/api/storefront/v1/projects/4567/processing-results" \
  -H  "accept: text/plain" \
  -H  "Authorization: Bearer <TOKEN>"
```

**HTTP**

```bash
# First, send this request. 

POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/render-hires?storefrontId=12
Content-Type: application/json
{
    "ownerId": userId,
    "scenario": { "designId": stateId, "name": "print"}
}

# Then, poll the project's status to see if it is completed or failed.

GET https://api.customerscanvashub.com/api/storefront/v1/projects/4567/processing-results
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, a design ID, and a file name.
var body = """
{
    "ownerId": userId,
    "scenario": { "designId": stateId, "name": "print"}
}
""";
var project = await projectsApiClient.CreateByRenderHiResScenarioAsync(storefrontId, null, body);
// Poll the project status.
do
{
    // Wait for 15 seconds.
    await Task.Delay(TimeSpan.FromSeconds(15));
    // Get the project status.
    var result = await projectsApiClient.GetProjectProcessingResultsAsync(project.id);
    // Get the print file URL or show the exception.
    if (result.Status == ProjectProcessingStatus.Completed)
    {
        return result.OutputFileDetails.First().Url;
    } 
    else if (result.Status == ProjectProcessingStatus.Failed)
    {
        throw new Exception("The rendering job failed");
    }
} while (true);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, a design ID, and a file name.
var body = {
    "ownerId": userId,
    "scenario": { "designId": stateId, "name": "print"}
};
var project = _projectsApiClient.createByRenderHiResScenario(storefrontId, null, JSON.stringify(body));
// Poll the project status.
await pollProject(project.id);
async function pollProject(projectId: string): Promise<string> {
    while (true) {
        // Wait for 15 seconds.
        await delay(15000);
        // Get the project status.
        const result = _projectsApiClient.getProjectProcessingResults(projectId);
        // Get the print file URL or show the exception.
        if (result.status === ProjectProcessingStatus.Completed) {
            return result.outputFileDetails[0].url;
        } else if (result.status === ProjectProcessingStatus.Failed) {
            throw new Error("The rendering job failed");
        }
    }
}
```

**PHP**

```php
// Set a real storefront ID.
$storefrontId = 12;
// Set an owner ID, a design ID, and a file name.
$body = "{'ownerId': '6527afc635b52bfadd355226', 'scenario': { 'designId': stateId, 'name': 'print'}}";
$project = $projectsApiClient->projectsCreateByRenderHiResScenario($storefrontId, null, $body);
// Poll the project status.
do {
    // Wait for 15 seconds.
    sleep(15);
    // Get the project status.
    $result = $projectsApiClient->projectsGetProjectProcessingResults($project->id);
    // Get the print file URL or show the exception.
    if ($result->status === ProjectProcessingStatus::COMPLETED) {
        return $result->outputFileDetails[0]->url;
    } elseif ($result->status === ProjectProcessingStatus::FAILED) {
        throw new Exception("The rendering job failed");
    }
} while (true);
```

### Deprecated endpoints

If you integrated earlier than March 2023, you most likely created a project and received a URL from the project using the endpoints `/api​/storefront​/v1​/projects​/{id}​/project-pdf` or `/api/storefront/v1/projects/{id}/project-pdf-zip`, which returned a URL linking to a ZIP with a print file. For example,

```bash
curl -X \
GET "https://api.customerscanvashub.com/api/storefront/v1/projects/1/project-pdf?designUserId=&designId=" \
-H 'X-Api-Key: YOUR_TOKEN'
```

:::important
This method is deprecated now and no longer recommended. Instead, we strongly advise using the [processing 
pipelines](#rendering-print-ready-files).
:::

Here, you can use the `/api/storefront/v1/projects/by-scenario/render-hires` endpoint and pass `designUserId` as `ownerId` together with `designId` in the request body, as described in the previous example.

If you received a zipped print file instead of a link to it, you can create a custom pipeline delivering a print file, and then use the [/api/storefront/v1/projects/by-scenario/specific-pipeline](#custom-pipelines-with-invoke-url) endpoint. For example, if the pipeline ID is `1234`, then your calls may look as follows:

**cURL**

```bash
# First, send this request. 

curl -X \
POST "https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12" \
-H  "accept: text/plain" \
-H  "Content-Type: application/json" \
-H  "Authorization: Bearer <TOKEN>" \
-d  '{
      "ownerId": designUserId,
      "items": [{
          "designIds": [designId]
      }],
      "scenario": {"pipelineId": 1234}
    }'

# Then, poll the project's status to see if it is completed or failed.

curl -X \
  GET "https://api.customerscanvashub.com/api/storefront/v1/projects/4567/processing-results" \
  -H  "accept: text/plain" \
  -H  "Authorization: Bearer <TOKEN>"
```

**HTTP**

```bash
# First, send this request. 

POST https://api.customerscanvashub.com/api/storefront/v1/projects/by-scenario/specific-pipeline?storefrontId=12
Content-Type: application/json
{
    "ownerId": designUserId,
    "items": [{
        "designIds": [designId]
    }],
    "scenario": {"pipelineId": 1234}
}

# Then, poll the project's status to see if it is completed or failed.

GET https://api.customerscanvashub.com/api/storefront/v1/projects/4567/processing-results
```

**C#**

```cs
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, design IDs, and a pipeline ID.
var body = """
{
    "ownerId": designUserId,
    "items": [{
        "designIds": [designId]
    }],
    "scenario": {"pipelineId": 1234}
}
""";
var project = await projectsApiClient.CreateBySpecificPipelineScenarioAsync(storefrontId, null, body);
// Poll the project status.
do
{
    // Wait for 15 seconds.
    await Task.Delay(TimeSpan.FromSeconds(15));
    // Get the project status.
    var result = await projectsApiClient.GetProjectProcessingResultsAsync(project.id);
    // Get the print file URL or show the exception.
    if (result.Status == ProjectProcessingStatus.Completed)
    {
        return result.OutputFileDetails.First().Url;
    } 
    else if (result.Status == ProjectProcessingStatus.Failed)
    {
        throw new Exception("The rendering job failed");
    }
} while (true);
```

**TS**

```ts
// Set a real storefront ID.
var storefrontId = 12;
// Set an owner ID, design IDs, and a pipeline ID.
var body = {
    "ownerId": designUserId,
    "items": [{
        "designIds": [designId]
    }],
    "scenario": {"pipelineId": 1234}
};
var project = _projectsApiClient.createBySpecificPipelineScenario(storefrontId, null, JSON.stringify(body));
// Poll the project status.
await pollProject(project.id);
async function pollProject(projectId: string): Promise<string> {
    while (true) {
        // Wait for 15 seconds.
        await delay(15000);
        // Get the project status.
        const result = _projectsApiClient.getProjectProcessingResults(projectId);
        // Get the print file URL or show the exception.
        if (result.status === ProjectProcessingStatus.Completed) {
            return result.outputFileDetails[0].url;
        } else if (result.status === ProjectProcessingStatus.Failed) {
            throw new Error("The rendering job failed");
        }
    }
}
```

**PHP**

Not available yet.

***

**Further Reading**
- Explore how to [register storefronts](https://customerscanvas.com/docs/hub/dev-center/api/storefront/register-storefront.md) in your Customer's Canvas account.
- Learn how to [use projects to render designs](https://customerscanvas.com/docs/hub/dev-center/api/storefront/working-with-projects.md).
- Dive into [Storefront API](https://customerscanvas.com/docs/hub/dev-center/api/storefront.md) reference.
