# Submit a product for review

For designers.

Uploads a design file and creates a private product from it. 3DAPI then reviews the design, which a person does, so do not poll for it: wait for the `product.approved` or `product.rejected` webhook (or check `review.status` later), then run `publish_product`.

Variants, images and attachments can be sent in the same `create_product` call; this workflow keeps the minimum.

## What you need

-   `name` (string, required): Product name.
-   `file_name` (string, required): Name of the design file, for example `tracker.3mf`.
-   `content_type` (string, required): MIME type of the design file: `model/3mf`, `model/stl`, `model/obj` or `application/octet-stream`.
-   `size_bytes` (integer, required): Size of the design file in bytes, up to 150 MB.
-   `materials` (array of string, required): Catalog material ids (`mat_...`) the design may be printed in. `list_materials` lists them.
-   `idempotency_key` (string): A unique string for this attempt, for example a UUID. Send the same value when you retry, so the write happens once.

## Step 1: Start a file upload

[POST `/v2/uploads`](https://public-api.dapi.digital/docs/reference/uploads.md#create_upload)

Reserve an upload for the design file. Then send the file bytes to `upload_url` with the returned `method` and `headers`. That transfer goes straight to storage, not to this API, so it is not a step here; do it before the next step.

**Request body**

```json
{
  "purpose": "design_file",
  "file_name": "$inputs.file_name",
  "content_type": "$inputs.content_type",
  "size_bytes": "$inputs.size_bytes"
}
```

Succeeds when:

-   `$statusCode == 201`

Keeps for the next steps:

-   `upload_id`: `$response.body#/id`
-   `upload_url`: `$response.body#/upload_url`
-   `method`: `$response.body#/method`
-   `headers`: `$response.body#/headers`

## Step 2: Create a product

[POST `/v2/products`](https://public-api.dapi.digital/docs/reference/products.md#create_product)

Create the private product with the uploaded file as its design. It starts with `review.status: pending`. A full plan is refused with `plan_limit_reached`, whose `details.fix_url` opens the upgrade page; a file that never reached `upload_url` is refused with `upload_missing`.

**Parameters**

```json
{
  "header Idempotency-Key": "$inputs.idempotency_key"
}
```

**Request body**

```json
{
  "name": "$inputs.name",
  "designs": [
    {
      "key": "main",
      "file": {
        "upload": "$steps.upload_design.outputs.upload_id"
      },
      "materials": {
        "mode": "single",
        "allowed": "$inputs.materials"
      }
    }
  ]
}
```

Succeeds when:

-   `$statusCode == 201`

Otherwise:

-   `retry_server_error`: retry after 5 seconds, at most 3 times, when `$statusCode >= 500`.
-   `plan_limit_reached`: stop the workflow when `$statusCode == 403`.

Keeps for the next steps:

-   `product_id`: `$response.body#/id`
-   `review_status`: `$response.body#/review/status`

## As a workflow file

The workflow returns:

-   `product_id`: `$steps.create_product.outputs.product_id`
-   `review_status`: `$steps.create_product.outputs.review_status`

Values starting with `$` are filled in as you go: `$inputs.x` is something you provide, `$steps.s.outputs.o` is a value an earlier step returned, and `$response.body#/path` a field of this response.

This recipe is the workflow `submit_product` of [arazzo.json](https://public-api.dapi.digital/docs/arazzo.json), an [Arazzo 1.0](https://spec.openapis.org/arazzo/latest.html) document that tools and AI agents can follow step by step.

---

3DAPI API Docs. This page on the web: https://public-api.dapi.digital/docs/guides/submit-product. Index of every page: https://public-api.dapi.digital/docs/llms.txt.
