# Products

What you sell and its variants.

This page covers [the product object](https://public-api.dapi.digital/docs/reference/products.md#product_object), [the variant object](https://public-api.dapi.digital/docs/reference/products.md#variant_object) and [the product compliance object](https://public-api.dapi.digital/docs/reference/products.md#product_compliance_object).

## Endpoints

-   [GET `/v2/products`](https://public-api.dapi.digital/docs/reference/products.md#list_products)
-   [POST `/v2/products`](https://public-api.dapi.digital/docs/reference/products.md#create_product)
-   [GET `/v2/products/{id}`](https://public-api.dapi.digital/docs/reference/products.md#get_product)
-   [PATCH `/v2/products/{id}`](https://public-api.dapi.digital/docs/reference/products.md#update_product)
-   [DELETE `/v2/products/{id}`](https://public-api.dapi.digital/docs/reference/products.md#delete_product)
-   [POST `/v2/products/{id}/publish`](https://public-api.dapi.digital/docs/reference/products.md#publish_product)
-   [POST `/v2/products/{id}/unpublish`](https://public-api.dapi.digital/docs/reference/products.md#unpublish_product)
-   [POST `/v2/products/{id}/variants`](https://public-api.dapi.digital/docs/reference/products.md#create_variant)
-   [PATCH `/v2/variants/{id}`](https://public-api.dapi.digital/docs/reference/products.md#update_variant)
-   [DELETE `/v2/variants/{id}`](https://public-api.dapi.digital/docs/reference/products.md#delete_variant)
-   [POST `/v2/products/{id}/compliance/documents`](https://public-api.dapi.digital/docs/reference/products.md#upload_compliance_document)
-   [DELETE `/v2/compliance-documents/{id}`](https://public-api.dapi.digital/docs/reference/products.md#delete_compliance_document)
-   [GET `/v2/products/{id}/compliance`](https://public-api.dapi.digital/docs/reference/products.md#get_product_compliance)

## The product object

Something you sell: its variants, what they are made of, their prices and margins, and its storefront and review state.

### Attributes

-   `id` (string): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `product`.
-   `name` (string): Display name.
-   `slug` (string): URL-safe name used in the storefront address.
-   `category` (object or null): The product category, which decides its safety requirements. Null when none is selected.
    -   Shape **Reference** (default):
        
        Reference to a category. Pass the matching `expand[]` path to receive the full category instead.
        
        -   `id` (string): Unique identifier of the category, prefixed with `cat_`. Treat it as an opaque string: store it and compare it, never parse it.
        -   `object` (string): Names the type of this object. Always `category`.
        -   `name` (string): Display name of the category.
    -   Shape **Category** (expanded): [the category object](https://public-api.dapi.digital/docs/reference/categories.md#category_object).
        
-   `review` (ProductReview): Review of the product as a whole: approved once every design and extra in it is approved, rejected if any is rejected.
    -   `status` (ReviewStatus):
        
        Where 3DAPI stands on reviewing this object:
        
        -   `pending`: waiting for review by 3DAPI. Nothing to do on your side.
        -   `approved`: reviewed and accepted.
        -   `rejected`: reviewed and refused. `reason` says why; fix it and save again to resubmit.
        
    -   `reason` (string or null): Why it was rejected. Null unless `status` is `rejected`.
-   `publishing` (ProductPublishing): Storefront visibility.
    -   `is_public` (boolean): Whether the product is listed on your storefront. A public product can only be ordered once `review.status` is `approved`.
    -   `unpublished_by` (string or null): Who made a private product private. `platform` means 3DAPI unpublished it, for example after a plan downgrade put you over your public product limit; upgrading can restore it. Null while public.
        
        Possible values: `seller`, `platform`.
        
    -   `can_publish` (boolean): True when nothing blocks making the product public.
    -   `blockers` (array of PublishBlocker): Everything that currently blocks publishing, in the order to fix them. Empty when `can_publish` is true.
        -   `code` (string):
            
            Why the product cannot be made public:
            
            -   `onboarding_incomplete`: store onboarding (billing and store details) is not finished.
            -   `not_registered_business`: products can only be sold by a registered business; update your billing details.
            -   `public_store_disabled`: your public store is turned off in your store settings.
            -   `no_design`: the product has no design file.
            -   `files_not_approved`: 3DAPI has not approved the design files yet.
            -   `category_required`: the product needs a category.
            -   `compliance_documents_missing`: a document required by the category is missing or not approved.
            
        -   `message` (string): What to do about it, ready to show to a person.
-   `variant_count` (integer): Number of variants.
    
    Range at least 0.
    
-   `price_range` (object or null): Lowest and highest variant price. Null without variants.
    -   `min` (object): Lowest variant price.
        -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
        -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
    -   `max` (object): Highest variant price.
        -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
        -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
-   `images` (array of Image): Product photos.
    -   `url` (string): Public image URL.
    -   `primary` (boolean): True for the main image, shown first. At most one.
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `updated_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `description` (string or null): Product description shown on the storefront.
-   `options` (array of object): The choices a buyer makes, in display order. Each variant has one value per option.
    -   `name` (string): Option name.
    -   `values` (array of string): Possible values, in display order.
-   `variants` (array of Variant): Every variant.
    
    Described in [the variant object](https://public-api.dapi.digital/docs/reference/products.md#variant_object).
    
-   `attachments` (array of objects): Production documents sent to the production partner with every order.
    -   Shape **Reference** (default):
        
        Reference to a attachment. Pass the matching `expand[]` path to receive the full attachment instead.
        
        -   `id` (string): Unique identifier of the attachment, prefixed with `att_`. Treat it as an opaque string: store it and compare it, never parse it.
        -   `object` (string): Names the type of this object. Always `attachment`.
        -   `name` (string): Display name of the attachment.
    -   Shape **Attachment** (expanded): [the attachment object](https://public-api.dapi.digital/docs/reference/attachments.md#attachment_object).
        
-   `storefront` (object): How the product appears on your storefront.
    -   `url` (string or null): Public product page. Null while your store is not public.
    -   `show_compliance_labels` (boolean): Whether the category safety labels (warnings, age restriction, risk level, CE mark) are shown on the product page.
    -   `disclaimers` (array of object): Your disclaimers shown on the product page.
        -   `title` (string): Heading of the disclaimer.
        -   `text` (string): The disclaimer text.
-   `custom_label_url` (string or null): Your custom label image, printed by the production partner and shipped with the product. Null when none.
-   `compliance` (object): Safety documents at a glance. With `expand[]=compliance` it is the full view, identical to what the product compliance endpoint returns.
    -   Shape **ProductComplianceSummary** (expanded):
        
        Compliance at a glance. Pass `expand[]=compliance` for every requirement and document.
        
        -   `object` (string): Names the type of this object. Always `product_compliance`.
        -   `enforced` (boolean): Whether missing documents block publishing this product right now. Only EU/UK sellers are held to it, and only while document enforcement is switched on by 3DAPI; when true, the product `publishing.blockers` include `compliance_documents_missing` until `complete` is true.
        -   `complete` (boolean): True when every requirement is approved.
        -   `missing` (integer): Requirements without an approved document.
            
            Range at least 0.
            
    -   Shape **ProductCompliance** (expanded): [the product compliance object](https://public-api.dapi.digital/docs/reference/products.md#product_compliance_object).
        

**The product object**

```json
{
  "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "product",
  "name": "PR Tracker",
  "slug": "pr-tracker",
  "category": {
    "id": "cat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "category",
    "name": "Desk accessories"
  },
  "review": {
    "status": "approved",
    "reason": null
  },
  "publishing": {
    "is_public": true,
    "unpublished_by": null,
    "can_publish": true,
    "blockers": []
  },
  "variant_count": 1,
  "price_range": {
    "min": {
      "amount": 5000,
      "currency": "usd"
    },
    "max": {
      "amount": 5000,
      "currency": "usd"
    }
  },
  "images": [
    {
      "url": "https://cdn.example.com/tracker.png",
      "primary": true
    }
  ],
  "created_at": "2026-04-21T07:34:06Z",
  "updated_at": "2026-10-02T11:12:40Z",
  "description": "A desk tracker for your personal records.",
  "options": [
    {
      "name": "Color",
      "values": [
        "Black",
        "Blue"
      ]
    }
  ],
  "variants": [
    {
      "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "variant",
      "sku": "TRACKER-BLUE",
      "name": "PR Tracker Blue",
      "status": "active",
      "options": [
        {
          "name": "Color",
          "value": "Blue"
        }
      ],
      "category": null,
      "pricing": {
        "price": {
          "amount": 5000,
          "currency": "usd"
        },
        "prices_include_vat": true,
        "costs": {
          "manufacturing": {
            "amount": 332,
            "currency": "usd"
          },
          "extra_parts": {
            "amount": 40,
            "currency": "usd"
          },
          "platform_fee": {
            "amount": 250,
            "currency": "usd"
          }
        },
        "earnings": {
          "amount": 4378,
          "currency": "usd"
        }
      },
      "shipping": {
        "tier": "medium",
        "determined_by": "volume",
        "chargeable_weight_grams": 820,
        "extra_small": false
      },
      "contents": [
        {
          "type": "design",
          "quantity": 1,
          "design": {
            "id": "dsg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
            "object": "design",
            "name": "tracker.3mf"
          },
          "materials": [
            {
              "id": "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
              "object": "material",
              "name": "PLA Blue",
              "custom": false
            }
          ]
        },
        {
          "type": "extra",
          "quantity": 4,
          "unit_price": {
            "amount": 10,
            "currency": "usd"
          },
          "extra": {
            "id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
            "object": "extra",
            "name": "M3 screw"
          }
        }
      ],
      "image": null
    }
  ],
  "attachments": [
    {
      "id": "att_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "attachment",
      "name": "assembly-guide.pdf"
    }
  ],
  "storefront": {
    "url": "https://dapi.digital/store/acme/pr-tracker",
    "show_compliance_labels": true,
    "disclaimers": [
      {
        "title": "Production safety",
        "text": "Keep away from heat."
      }
    ]
  },
  "custom_label_url": null,
  "compliance": {
    "object": "product_compliance",
    "enforced": false,
    "complete": false,
    "missing": 1
  }
}
```

## List products

GET `/v2/products`

- **Credentials**: An API key with the `products:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `list_products`

Lists your products, newest first by default. Items leave out variants and storefront details to stay small; get the product for everything, or pass `expand[]=variants`. Designers only.

### Query parameters

-   `review_status` (array of ReviewStatus): Only products in these review states. Repeat for several, for example `review_status=pending&review_status=rejected`. Same shape as `ReviewStatus` above.
-   `is_public` (string): Only public (`true`) or private (`false`) products.
-   `category` (string): Only products in this category.
-   `search` (string): Only products whose name contains this text, ignoring case.
-   `sku` (string): Only the product with a variant whose `sku` is exactly this, ignoring case. Use it to match a line from another platform (Etsy, Shopify, WooCommerce) to your variant; find the variant in `expand[]=variants`.
-   `updated_after` (timestamp): Only products changed after this time. Use it to sync changes since your last poll.
-   `sort` (string): Order of results. A leading `-` means descending. `position` is your own order from the dashboard. `name` sorts alphabetically in a language-aware collation, which compares letters before spaces and case (`test slicer` comes after `testing warnings`); do not expect byte order.
    
    Possible values: `-created_at`, `created_at`, `-updated_at`, `name`, `position`.
    
    Defaults to "-created\_at".
    
-   `limit` (integer): How many results per page, from 1 to 100.
    
    Range 1 to 100. Defaults to 20.
    
-   `cursor` (string): Where to continue from: the `next_cursor` of the previous page. Omit for the first page.
-   `expand[]` (array of string): Replace references with full objects. Repeat the parameter for several paths, for example `expand[]=variants`. Unknown paths are rejected with `invalid_expand`. An expanded object from another resource needs the read scope of that resource on your API key; without it the reference `{ id, object, name }` is returned instead, with no error. Here `variants.contents.extra` needs `extras:read`, `variants.contents.stock` needs `stocks:read`.
    
    Possible values: `variants`, `category`, `variants.category`, `variants.contents.design`, `variants.contents.stock`, `variants.contents.extra`, `variants.contents.materials`.
    

### Returns 200 OK

A page of products.

Response attributes (ProductList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of ProductSummary): The page of results.
    -   `id` (string): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `product`.
    -   `name` (string): Display name.
    -   `slug` (string): URL-safe name used in the storefront address.
    -   `category` (object or null): The product category, which decides its safety requirements. Null when none is selected.
        -   Shape **Reference** (default):
            
            Same shape as `CategoryReference` above.
            
        -   Shape **Category** (expanded): [the category object](https://public-api.dapi.digital/docs/reference/categories.md#category_object).
            
    -   `review` (ProductReview): Same shape as `ProductReview` above.
    -   `publishing` (ProductPublishing): Same shape as `ProductPublishing` above.
    -   `variant_count` (integer): Number of variants.
        
        Range at least 0.
        
    -   `price_range` (object or null): Lowest and highest variant price. Null without variants.
        -   `min` (object): Lowest variant price.
            -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
            -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
        -   `max` (object): Highest variant price.
            -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
            -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
    -   `images` (array of Image): Product photos. Same shape as `Image` above.
    -   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
    -   `updated_at` (timestamp): Point in time, ISO 8601 in UTC.
    -   `variants` (array of Variant, optional): Only present with `expand[]=variants`.
        
        Described in [the variant object](https://public-api.dapi.digital/docs/reference/products.md#variant_object).
        
-   `has_more` (boolean): True when more results exist after this page. Fetch them by passing `next_cursor` as `cursor`.
-   `next_cursor` (string or null): Opaque cursor for the next page, or null on the last page. Pass it back unchanged as `cursor`.

### Responses

-   `200`: A page of products.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/products" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "product",
      "name": "PR Tracker",
      "slug": "pr-tracker",
      "category": {
        "id": "cat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "category",
        "name": "Desk accessories"
      },
      "review": {
        "status": "approved",
        "reason": null
      },
      "publishing": {
        "is_public": true,
        "unpublished_by": null,
        "can_publish": true,
        "blockers": []
      },
      "variant_count": 1,
      "price_range": {
        "min": {
          "amount": 5000,
          "currency": "usd"
        },
        "max": {
          "amount": 5000,
          "currency": "usd"
        }
      },
      "images": [
        {
          "url": "https://cdn.example.com/tracker.png",
          "primary": true
        }
      ],
      "created_at": "2026-04-21T07:34:06Z",
      "updated_at": "2026-10-02T11:12:40Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Create a product

POST `/v2/products`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `create_product`

Creates a product with its design files, and optionally its images, attachments and variants, in one request. It is private and its designs are pending review by 3DAPI; publish it once it is approved. Upload files first (purposes `design_file`, `product_image`, `attachment`). Counts toward your plan product limit. Designers only.

### Headers

-   `Idempotency-Key` (string): Any unique string, for example a UUID. Sending the same request again with the same key within 24 hours returns the first response without doing anything twice. Reusing a key with a different body is refused with `idempotency_key_reused`. A refused request (4xx) frees the key so you can correct and resend it; a server error (5xx) is kept and returned again, because the write may already have happened: check the resource before retrying with a new key.

### Request body

-   `name` (string, required): Display name, shown on the storefront.
-   `slug` (string): URL-safe name for the storefront address, unique across your products. Generated from the name when omitted.
-   `description` (string or null): Product description shown on the storefront. Null for none.
-   `category` (string or null): The product category, which decides its safety requirements.
-   `prices_include_vat` (boolean or null): For EU and UK buyers: `true` prices include VAT, `false` VAT is added on top, `null` inherits your store setting.
-   `options` (array of object): The choices buyers make, in display order.
    -   `name` (string, required): Option name.
    -   `values` (array of string, required): Possible values, in display order. Removing a value a variant still uses is refused (`option_in_use`).
-   `disclaimers` (array of object): Your disclaimers shown on the product page.
    -   `title` (string, required): Heading of the disclaimer.
    -   `text` (string, required): The disclaimer text. An entry whose text is blank once trimmed is dropped.
-   `show_compliance_labels` (boolean): Whether the category safety labels (warnings, age restriction, risk level, CE mark) are shown on the product page. `true` when omitted on create.
-   `images` (array of objects): Product photos, in display order. The list replaces the current images: entries not sent are deleted.
    -   Shape **object**:
        
        -   `upload` (string, required): An upload id from `POST /v2/uploads`, used before it expires. The file is checked when used: one that is not what its name and type say is refused with 422 `invalid_file` (the expected type in `details.expected`) and must be uploaded again.
        -   `primary` (boolean): Makes this the main image. At most one entry may be marked. Without a marked entry, a kept main image stays main; otherwise one is chosen for you.
    -   Shape **object**:
        
        -   `url` (string, required): The URL of an image this product or stock already has, to keep it.
        -   `primary` (boolean): Makes this the main image. At most one entry may be marked. Without a marked entry, a kept main image stays main; otherwise one is chosen for you.
-   `custom_label` (object or null): A label the production partner prints and ships with the product: new (`upload`, purpose `product_image`) or kept (`url`). Null removes it.
    -   Shape **Reference** (default):
        
        -   `upload` (string, required): An upload id from `POST /v2/uploads`, used before it expires. The file is checked when used: one that is not what its name and type say is refused with 422 `invalid_file` (the expected type in `details.expected`) and must be uploaded again.
    -   Shape **object**:
        
        -   `url` (string, required): The URL of the label the product already has.
-   `attachments` (array of objects): Production documents for the farm: new (`upload`, purpose `attachment`) or kept (`id`).
    -   Shape **Reference** (default):
        
        Same shape as `UploadReference` above.
        
    -   Shape **object**:
        
        -   `id` (string, required): An attachment to keep.
-   `manufacturing` (object): How the product is produced. Applies to every design.
    -   `assembly_required` (boolean, required): Whether the printed parts must be assembled before shipping. Turning it on or off sends every design back to review.
    -   `farm_instructions` (string or null): Notes for the production partner, for example assembly steps. Omitted keeps the current notes; null or blank clears them.
-   `designs` (array of object, required): The model files of the product.
    -   `key` (string, required): Your name for this design within the request, for variants to reference it.
    -   `file` (object, required): The model file, uploaded first with purpose `design_file`.
        -   `upload` (string, required): An upload id from `POST /v2/uploads`, used before it expires. The file is checked when used: one that is not what its name and type say is refused with 422 `invalid_file` (the expected type in `details.expected`) and must be uploaded again.
    -   `name` (string or null): Display name. The file name when omitted.
    -   `materials` (object, required)
        -   Shape **mode: single**:
            
            -   `mode` (string, required): `single`: the design prints in one material, and each variant picks one from `allowed`.
            -   `allowed` (array of objects, required): Materials variants may print this design in; each variant picks one.
                -   Shape **string**:
                    
                    Unique identifier of the material, prefixed with `mat_`. Treat it as an opaque string: store it and compare it, never parse it.
                    
                -   Shape **CustomMaterialInput** (expanded):
                    
                    -   `custom` (string, required): A filament not in the catalog, by name. Reviewed by 3DAPI.
        -   Shape **mode: multicolor**:
            
            -   `mode` (string, required): `multicolor`: the design prints in several color slots, and each variant picks one of the `combinations`.
            -   `combinations` (array of object, required): Color combinations variants may print this design in. Every combination has the same number of slots; each variant picks one by sending its slot materials in order.
                -   `name` (string or null): Display name. Built from the slot materials when omitted.
                -   `slots` (array of objects, required): One material per color slot, in the order the file uses them. Same shape as `MaterialInput` above.
-   `variants` (array of CreateVariantRequest): Variants to create with the product. Up to 200 per product.
    -   `sku` (string, required): Your SKU code, unique across your active variants.
    -   `name` (string or null): Display name of the variant. Null for none.
    -   `options` (array of object): One value per product option, using the product option names and values.
        -   `name` (string, required): An option name of the product.
        -   `value` (string, required): One of the values of that option.
    -   `price` (object, required): What the buyer pays, at most $100,000.00. Not below the minimum, which covers production, a platform fee of up to 10% and the card fee at its highest rate (2.9% + $0.30, the buyer is not known yet): minimum = (manufacturing + extra parts + $0.30) / (0.90 - 0.029), rounded up to the cent, or no minimum while nothing has a production price yet. A lower price is refused with `price_below_minimum`, with the minimum in `details.minimum`. Checked when the price or the contents change, so variants saved before this rule keep their price until edited.
        -   `amount` (integer, required): Amount in cents, at most 10000000 ($100,000.00). 5000 means $50.00.
            
            Range at most 10000000.
            
        -   `currency` (string, required): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
    -   `prices_include_vat` (boolean or null): For EU and UK buyers: `true` the price includes VAT, `false` VAT is added on top, `null` inherits from the product, then your store setting.
    -   `category` (string or null): Overrides the product category for this variant. Null uses the product category.
    -   `contents` (array of objects, required): What each unit is made of: at least one design or one stock.
        -   Shape **type: design**:
            
            -   `type` (string, required): `design`: a design of this product, printed for the variant.
            -   `design` (string, required): A design id (`dsg_...`), or, inside `create_product`, the `key` of a design created in the same request.
            -   `quantity` (integer, required): Copies of the design in one unit of the variant.
                
                Range 1 to 100.
                
            -   `materials` (array of objects, required): One material for a single-material design, or one per color slot for a multicolor design, in slot order. Each must be allowed by the design. Same shape as `MaterialInput` above.
        -   Shape **type: extra**:
            
            -   `type` (string, required): `extra`: one of your extras, packed with the variant.
            -   `extra` (string, required): One of your extras. An extra still pending review is accepted and sends the product designs back to review; a rejected one is refused (`extra_not_approved`).
            -   `quantity` (integer, required): Units of the extra in one unit of the variant.
                
                Range 1 to 100.
                
        -   Shape **type: stock**:
            
            -   `type` (string, required): `stock`: one of your approved stocks.
            -   `stock` (string, required): Unique identifier of the stock, prefixed with `stk_`. Treat it as an opaque string: store it and compare it, never parse it.
            -   `quantity` (integer, required): Stock units used by one unit of the variant.
                
                Range 1 to 100.
                
            -   `consumes_stock` (boolean): Ship this variant from the stock.
                
                Defaults to true.
                
    -   `image` (object or null): A variant image, new (`upload`) or kept (`url`). Null for none.
        -   Shape **Reference** (default):
            
            Same shape as `UploadReference` above.
            
        -   Shape **object**:
            
            -   `url` (string, required): The URL of one of the product images, to use as the variant image.

### Returns 201 Created

The new product, private and pending review. Returns [the product object](https://public-api.dapi.digital/docs/reference/products.md#product_object).

### Responses

-   `201`: The new product, private and pending review.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: Your plan product limit is reached (`plan_limit_reached`); `details.fix_url` points at upgrading.
-   `409`: The slug or a SKU is already used (`slug_taken`, `sku_already_used`).
-   `422`: A variant breaks a product rule: `option_mismatch`, `material_not_allowed`, `material_not_found`, `extra_not_found`, `extra_not_approved` (a rejected extra), `stock_not_found`, `stock_not_approved`, `category_not_found`, `variant_needs_design_or_stock`, `price_below_minimum` (minimum in `details.minimum`), `currency_not_supported`, `unknown_design_key`, `duplicate_design_key`, `design_not_in_product`, `too_many_variants`; a design names a combination with the wrong slot count (`combination_slots_mismatch`); an image or attachment to keep is not the product (`image_not_found`, `attachment_not_found`, `custom_label_not_found`, `too_many_primary_images`); or an upload cannot be used: `unknown_upload`, `upload_expired`, `upload_purpose_mismatch`, `upload_missing` (an upload id whose file was never sent), `upload_already_used` (an upload id already used once; each works once).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/products" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "PR Tracker",
  "description": "A desk tracker for your personal records.",
  "category": "cat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "options": [
    {
      "name": "Color",
      "values": [
        "Black",
        "Blue"
      ]
    }
  ],
  "images": [
    {
      "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "primary": true
    }
  ],
  "manufacturing": {
    "assembly_required": true,
    "farm_instructions": null
  },
  "designs": [
    {
      "key": "body",
      "file": {
        "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
      },
      "name": "Tracker body",
      "materials": {
        "mode": "single",
        "allowed": [
          "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
        ]
      }
    }
  ],
  "variants": [
    {
      "sku": "TRACKER-BLUE",
      "name": "PR Tracker Blue",
      "options": [
        {
          "name": "Color",
          "value": "Blue"
        }
      ],
      "price": {
        "amount": 5000,
        "currency": "usd"
      },
      "contents": [
        {
          "type": "design",
          "design": "body",
          "quantity": 1,
          "materials": [
            "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
          ]
        }
      ]
    }
  ]
}'
```

## Get a product

GET `/v2/products/{id}`

- **Credentials**: An API key with the `products:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `get_product`

Returns one product with every variant, its contents, pricing and margins, shipping tier, and what blocks publishing. Related designs, stocks, extras, materials, categories and attachments are references; expand them with `expand[]`. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Query parameters

-   `expand[]` (array of string): Replace references with full objects. Repeat the parameter for several paths, for example `expand[]=category`. Unknown paths are rejected with `invalid_expand`. An expanded object from another resource needs the read scope of that resource on your API key; without it the reference `{ id, object, name }` is returned instead, with no error. Here `variants.contents.extra` needs `extras:read`, `variants.contents.stock` needs `stocks:read`.
    
    Possible values: `category`, `variants.category`, `variants.contents.design`, `variants.contents.stock`, `variants.contents.extra`, `variants.contents.materials`, `attachments`, `compliance`.
    

### Returns 200 OK

The product. Returns [the product object](https://public-api.dapi.digital/docs/reference/products.md#product_object).

### Responses

-   `200`: The product.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no product with this id (`product_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

## Update a product

PATCH `/v2/products/{id}`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `update_product`

Changes the product fields you send. Sends designs back to review by 3DAPI: a new design file; turning assembly on or off (every design); a newly added extra that is not approved yet (every design); and, per design, a change to its custom materials, single or multicolor mode, color count or multi-material (AMS) requirement. Nothing else does. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Request body

-   `name` (string): Display name, shown on the storefront.
-   `slug` (string): URL-safe name for the storefront address, unique across your products. Generated from the name when omitted.
-   `description` (string or null): Product description shown on the storefront. Null for none.
-   `category` (string or null): The product category, which decides its safety requirements.
-   `prices_include_vat` (boolean or null): For EU and UK buyers: `true` prices include VAT, `false` VAT is added on top, `null` inherits your store setting.
-   `options` (array of object): The choices buyers make, in display order.
    -   `name` (string, required): Option name.
    -   `values` (array of string, required): Possible values, in display order. Removing a value a variant still uses is refused (`option_in_use`).
-   `disclaimers` (array of object): Your disclaimers shown on the product page.
    -   `title` (string, required): Heading of the disclaimer.
    -   `text` (string, required): The disclaimer text. An entry whose text is blank once trimmed is dropped.
-   `show_compliance_labels` (boolean): Whether the category safety labels (warnings, age restriction, risk level, CE mark) are shown on the product page. `true` when omitted on create.
-   `images` (array of objects): Product photos, in display order. The list replaces the current images: entries not sent are deleted. Same shape as `ImageInput` above.
-   `custom_label` (object or null): A label the production partner prints and ships with the product: new (`upload`, purpose `product_image`) or kept (`url`). Null removes it.
    -   Shape **Reference** (default):
        
        Same shape as `UploadReference` above.
        
    -   Shape **object**:
        
        -   `url` (string, required): The URL of the label the product already has.
-   `attachments` (array of objects): Production documents for the farm: new (`upload`, purpose `attachment`) or kept (`id`).
    -   Shape **Reference** (default):
        
        Same shape as `UploadReference` above.
        
    -   Shape **object**:
        
        -   `id` (string, required): An attachment to keep.
-   `manufacturing` (object): How the product is produced. Applies to every design.
    -   `assembly_required` (boolean, required): Whether the printed parts must be assembled before shipping. Turning it on or off sends every design back to review.
    -   `farm_instructions` (string or null): Notes for the production partner, for example assembly steps. Omitted keeps the current notes; null or blank clears them.

### Returns 200 OK

The updated product. Returns [the product object](https://public-api.dapi.digital/docs/reference/products.md#product_object).

### Responses

-   `200`: The updated product.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no product with this id (`product_not_found`).
-   `409`: The slug is already used (`slug_taken`).
-   `422`: Removing an option value a variant still uses (`option_in_use`); `category_not_found`; an image or attachment to keep is not the product (`image_not_found`, `attachment_not_found`, `custom_label_not_found`, `too_many_primary_images`); or an upload cannot be used: `unknown_upload`, `upload_expired`, `upload_purpose_mismatch`, `upload_missing` (an upload id whose file was never sent), `upload_already_used` (an upload id already used once; each works once).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X PATCH "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "PR Tracker v2",
  "category": "cat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "manufacturing": {
    "assembly_required": false,
    "farm_instructions": "Print the lid with the logo facing up."
  }
}'
```

## Delete a product

DELETE `/v2/products/{id}`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `delete_product`

Deletes a product, its designs, images and unused variants, as the dashboard does. Variants that orders reference are kept (marked deleted), and orders keep their own frozen copy of the product, so order history is never lost. Cannot be undone. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The product was deleted.

Response attributes (object):

-   `id` (string): The id of the deleted product.
-   `object` (string): Always `product`.
-   `deleted` (boolean): Always `true`: the deletion succeeded.

### Responses

-   `200`: The product was deleted.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no product with this id (`product_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X DELETE "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "product",
  "deleted": true
}
```

## Publish a product

POST `/v2/products/{id}/publish`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `publish_product`

Makes the product public on your storefront. Refused while anything in `publishing.blockers` remains, which includes an inactive store: you must be registered as a business and have your public store turned on. Counts toward your plan limit of public products. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Headers

-   `Idempotency-Key` (string): Any unique string, for example a UUID. Sending the same request again with the same key within 24 hours returns the first response without doing anything twice. Reusing a key with a different body is refused with `idempotency_key_reused`. A refused request (4xx) frees the key so you can correct and resend it; a server error (5xx) is kept and returned again, because the write may already have happened: check the resource before retrying with a new key.

### Returns 200 OK

The product, now public. Returns [the product object](https://public-api.dapi.digital/docs/reference/products.md#product_object).

### Responses

-   `200`: The product, now public.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: Your plan limit of public products is reached (`plan_limit_reached`); `details.fix_url` points at upgrading.
-   `404`: You have no product with this id (`product_not_found`).
-   `409`: Something blocks publishing (`cannot_publish`); `details.blockers` lists every blocker with its code and what to do, the same list as `publishing.blockers`.
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/publish" \
  -H "x-api-key: $DAPI_API_KEY"
```

## Unpublish a product

POST `/v2/products/{id}/unpublish`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `unpublish_product`

Takes the product off your storefront. Always allowed. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Headers

-   `Idempotency-Key` (string): Any unique string, for example a UUID. Sending the same request again with the same key within 24 hours returns the first response without doing anything twice. Reusing a key with a different body is refused with `idempotency_key_reused`. A refused request (4xx) frees the key so you can correct and resend it; a server error (5xx) is kept and returned again, because the write may already have happened: check the resource before retrying with a new key.

### Returns 200 OK

The product, now private. Returns [the product object](https://public-api.dapi.digital/docs/reference/products.md#product_object).

### Responses

-   `200`: The product, now private.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no product with this id (`product_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/unpublish" \
  -H "x-api-key: $DAPI_API_KEY"
```

## The variant object

An orderable version of a product with its own SKU, price and contents, for example one color.

### Attributes

-   `id` (string): Unique identifier of the variant, prefixed with `var_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `variant`.
-   `sku` (string): Your SKU code, unique across your active variants.
-   `name` (string or null): Display name of the variant.
-   `status` (string): `active` variants can be ordered; `inactive` ones are kept but not sold.
-   `options` (array of object): Option values, in the product option order.
    -   `name` (string): Option name, matching one in the product `options`.
    -   `value` (string): The value this variant has for that option.
-   `category` (object or null): A category that overrides the product category for this variant only. Null when the variant uses the product category.
    -   Shape **Reference** (default):
        
        Same shape as `CategoryReference` above.
        
    -   Shape **Category** (expanded): [the category object](https://public-api.dapi.digital/docs/reference/categories.md#category_object).
        
-   `pricing` (object): Price and margin for one unit.
    -   `price` (object): What the buyer pays.
        -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
        -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
    -   `prices_include_vat` (boolean): Whether `price` already includes VAT. Resolved from the variant, then the product, then your store setting.
    -   `costs` (object or null): What it costs to fulfil one unit. Null until 3DAPI has priced every part of the variant.
        -   `manufacturing` (object): Printing and production, extras excluded.
            -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
            -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
        -   `extra_parts` (object): The extras in the variant.
            -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
            -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
        -   `platform_fee` (object): 3DAPI commission, at your plan rate.
            -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
            -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
    -   `earnings` (object or null): What you earn per unit sold: `price` minus every cost. Null until the variant is priced.
        -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
        -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
-   `shipping` (object): How one unit is charged for shipping. Prices per tier and zone come from the shipping rates.
    -   `tier` (string or null): Shipping tier: `small` up to 600 g, `medium` 600 g to 2 kg, `large` above 2 kg. Null until every design in the variant has been measured.
    -   `determined_by` (string or null): Whether weight or size put the variant in its tier. Carriers charge for whichever is greater, so a bulky, light print can ship in a bigger tier. Null when `tier` is null.
        
        Possible values: `weight`, `volume`.
        
    -   `chargeable_weight_grams` (number or null): The weight the tier is based on: the greater of actual and volumetric weight. Null when `tier` is null.
    -   `extra_small` (boolean): True when the variant is light enough (under 100 g) that extra units in the same order ship without the additional-item charge.
-   `contents` (array of objects): What each unit of the variant is made of.
    -   Shape **type: design**:
        
        -   `type` (string): A printed part.
        -   `quantity` (integer): Copies of the design in one unit of the variant.
            
            Range at least 1.
            
        -   `design` (object): A design reference by default. With `expand[]=variants.contents.design` it is the full design, identical to what its own GET returns, when your API key has the read scope that GET needs; otherwise it stays a reference.
            -   Shape **Reference** (default):
                
                Reference to a design. Pass the matching `expand[]` path to receive the full design instead.
                
                -   `id` (string): Unique identifier of the design, prefixed with `dsg_`. Treat it as an opaque string: store it and compare it, never parse it.
                -   `object` (string): Names the type of this object. Always `design`.
                -   `name` (string): Display name of the design.
            -   Shape **Design** (expanded): [the design object](https://public-api.dapi.digital/docs/reference/designs.md#design_object).
                
        -   `materials` (array of objects): Materials this variant prints the design in: one for a single-material design, one per color slot for a multicolor design.
            -   Shape **Reference** (default):
                
                Reference to a material. Catalog materials can be expanded to the full material; custom materials have no id and nothing more to expand.
                
                -   `id` (string or null): Unique identifier of a catalog material, prefixed with `mat_`. Null for a custom material.
                -   `object` (string): Names the type of this object. Always `material`.
                -   `name` (string): Display name, for example `PLA Black`. For custom materials, exactly what the seller typed.
                -   `custom` (boolean): True for a seller-specified material that is not in the catalog. Custom materials need manual review before production.
            -   Shape **Material** (expanded): [the material object](https://public-api.dapi.digital/docs/reference/materials.md#material_object).
                
    -   Shape **type: stock**:
        
        -   `type` (string): Ready-made units taken from a stock.
        -   `quantity` (integer): Stock units used by one unit of the variant.
            
            Range at least 1.
            
        -   `consumes_stock` (boolean): True when the variant ships from this stock. False when the stock is linked but not used for fulfilment.
        -   `stock` (object): A stock reference by default. With `expand[]=variants.contents.stock` it is the full stock, identical to what its own GET returns, when your API key has the read scope that GET needs; otherwise it stays a reference.
            -   Shape **Reference** (default):
                
                Reference to a stock. Pass the matching `expand[]` path to receive the full stock instead.
                
                -   `id` (string): Unique identifier of the stock, prefixed with `stk_`. Treat it as an opaque string: store it and compare it, never parse it.
                -   `object` (string): Names the type of this object. Always `stock`.
                -   `name` (string): Display name of the stock.
            -   Shape **Stock** (expanded): [the stock object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_object).
                
    -   Shape **type: extra**:
        
        -   `type` (string): A bought-in part.
        -   `quantity` (integer): Units of the extra in one unit of the variant.
            
            Range at least 1.
            
        -   `unit_price` (object or null): What one unit of the extra adds to the variant cost. Null until the extra is priced.
            -   `amount` (integer): Amount in the smallest currency unit (cents for usd). 5000 means $50.00. Never a decimal.
            -   `currency` (string): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
        -   `extra` (object): A extra reference by default. With `expand[]=variants.contents.extra` it is the full extra, identical to what its own GET returns, when your API key has the read scope that GET needs; otherwise it stays a reference.
            -   Shape **Reference** (default):
                
                Reference to a extra. Pass the matching `expand[]` path to receive the full extra instead.
                
                -   `id` (string): Unique identifier of the extra, prefixed with `xtr_`. Treat it as an opaque string: store it and compare it, never parse it.
                -   `object` (string): Names the type of this object. Always `extra`.
                -   `name` (string): Display name of the extra.
            -   Shape **Extra** (expanded): [the extra object](https://public-api.dapi.digital/docs/reference/extras.md#extra_object).
                
-   `image` (object or null): Image specific to this variant, if any.
    -   `url` (string): Public image URL.

**The variant object**

```json
{
  "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "variant",
  "sku": "TRACKER-BLUE",
  "name": "PR Tracker Blue",
  "status": "active",
  "options": [
    {
      "name": "Color",
      "value": "Blue"
    }
  ],
  "category": null,
  "pricing": {
    "price": {
      "amount": 5000,
      "currency": "usd"
    },
    "prices_include_vat": true,
    "costs": {
      "manufacturing": {
        "amount": 332,
        "currency": "usd"
      },
      "extra_parts": {
        "amount": 40,
        "currency": "usd"
      },
      "platform_fee": {
        "amount": 250,
        "currency": "usd"
      }
    },
    "earnings": {
      "amount": 4378,
      "currency": "usd"
    }
  },
  "shipping": {
    "tier": "medium",
    "determined_by": "volume",
    "chargeable_weight_grams": 820,
    "extra_small": false
  },
  "contents": [
    {
      "type": "design",
      "quantity": 1,
      "design": {
        "id": "dsg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "design",
        "name": "tracker.3mf"
      },
      "materials": [
        {
          "id": "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "material",
          "name": "PLA Blue",
          "custom": false
        }
      ]
    },
    {
      "type": "extra",
      "quantity": 4,
      "unit_price": {
        "amount": 10,
        "currency": "usd"
      },
      "extra": {
        "id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "extra",
        "name": "M3 screw"
      }
    }
  ],
  "image": null
}
```

## Add a variant

POST `/v2/products/{id}/variants`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `create_variant`

Adds an orderable variant to a product: its SKU, option values, price and what it is made of. A newly added extra that is not approved yet sends the product designs back to review. Up to 200 variants per product. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Headers

-   `Idempotency-Key` (string): Any unique string, for example a UUID. Sending the same request again with the same key within 24 hours returns the first response without doing anything twice. Reusing a key with a different body is refused with `idempotency_key_reused`. A refused request (4xx) frees the key so you can correct and resend it; a server error (5xx) is kept and returned again, because the write may already have happened: check the resource before retrying with a new key.

### Request body

-   `sku` (string, required): Your SKU code, unique across your active variants.
-   `name` (string or null): Display name of the variant. Null for none.
-   `options` (array of object): One value per product option, using the product option names and values.
    -   `name` (string, required): An option name of the product.
    -   `value` (string, required): One of the values of that option.
-   `price` (object, required): What the buyer pays, at most $100,000.00. Not below the minimum, which covers production, a platform fee of up to 10% and the card fee at its highest rate (2.9% + $0.30, the buyer is not known yet): minimum = (manufacturing + extra parts + $0.30) / (0.90 - 0.029), rounded up to the cent, or no minimum while nothing has a production price yet. A lower price is refused with `price_below_minimum`, with the minimum in `details.minimum`. Checked when the price or the contents change, so variants saved before this rule keep their price until edited.
    -   `amount` (integer, required): Amount in cents, at most 10000000 ($100,000.00). 5000 means $50.00.
        
        Range at most 10000000.
        
    -   `currency` (string, required): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
-   `prices_include_vat` (boolean or null): For EU and UK buyers: `true` the price includes VAT, `false` VAT is added on top, `null` inherits from the product, then your store setting.
-   `category` (string or null): Overrides the product category for this variant. Null uses the product category.
-   `contents` (array of objects, required): What each unit is made of: at least one design or one stock. Same shape as `VariantContentInput` above.
-   `image` (object or null): A variant image, new (`upload`) or kept (`url`). Null for none.
    -   Shape **Reference** (default):
        
        Same shape as `UploadReference` above.
        
    -   Shape **object**:
        
        -   `url` (string, required): The URL of one of the product images, to use as the variant image.

### Returns 201 Created

The new variant. Returns [the variant object](https://public-api.dapi.digital/docs/reference/products.md#variant_object).

### Responses

-   `201`: The new variant.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no product with this id (`product_not_found`).
-   `409`: The SKU is already used (`sku_already_used`).
-   `422`: `option_mismatch`, `material_not_allowed`, `material_not_found`, `extra_not_found`, `extra_not_approved` (a rejected extra), `stock_not_found`, `stock_not_approved`, `category_not_found`, `variant_needs_design_or_stock`, `price_below_minimum` (minimum in `details.minimum`), `currency_not_supported`, `design_not_in_product` or `too_many_variants`; or the image upload cannot be used: `unknown_upload`, `upload_expired`, `upload_purpose_mismatch`, `upload_missing` (an upload id whose file was never sent), `upload_already_used` (an upload id already used once; each works once).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/variants" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "sku": "TRACKER-BLACK",
  "name": "PR Tracker Black",
  "options": [
    {
      "name": "Color",
      "value": "Black"
    }
  ],
  "price": {
    "amount": 5000,
    "currency": "usd"
  },
  "contents": [
    {
      "type": "design",
      "design": "dsg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "quantity": 1,
      "materials": [
        "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
      ]
    },
    {
      "type": "extra",
      "extra": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "quantity": 4
    }
  ]
}'
```

## Update a variant

PATCH `/v2/variants/{id}`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `update_variant`

Changes the variant fields you send. Set `status: inactive` to stop selling it without deleting it. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the variant, prefixed with `var_`. Treat it as an opaque string: store it and compare it, never parse it.

### Request body

-   `sku` (string): Your SKU code, unique across your active variants.
-   `name` (string or null): Display name of the variant. Null for none.
-   `options` (array of object): One value per product option, using the product option names and values.
    -   `name` (string, required): An option name of the product.
    -   `value` (string, required): One of the values of that option.
-   `price` (object): What the buyer pays, at most $100,000.00. Not below the minimum, which covers production, a platform fee of up to 10% and the card fee at its highest rate (2.9% + $0.30, the buyer is not known yet): minimum = (manufacturing + extra parts + $0.30) / (0.90 - 0.029), rounded up to the cent, or no minimum while nothing has a production price yet. A lower price is refused with `price_below_minimum`, with the minimum in `details.minimum`. Checked when the price or the contents change, so variants saved before this rule keep their price until edited.
    -   `amount` (integer, required): Amount in cents, at most 10000000 ($100,000.00). 5000 means $50.00.
        
        Range at most 10000000.
        
    -   `currency` (string, required): Three-letter ISO 4217 currency code, lowercase. Every amount is `usd` today.
-   `prices_include_vat` (boolean or null): For EU and UK buyers: `true` the price includes VAT, `false` VAT is added on top, `null` inherits from the product, then your store setting.
-   `category` (string or null): Overrides the product category for this variant. Null uses the product category.
-   `contents` (array of objects): What each unit is made of: at least one design or one stock. Same shape as `VariantContentInput` above.
-   `image` (object or null): A variant image, new (`upload`) or kept (`url`). Null for none.
    -   Shape **Reference** (default):
        
        Same shape as `UploadReference` above.
        
    -   Shape **object**:
        
        -   `url` (string, required): The URL of one of the product images, to use as the variant image.
-   `status` (string): `inactive` keeps the variant but stops selling it.
    
    Possible values: `active`, `inactive`.
    

### Returns 200 OK

The updated variant. Returns [the variant object](https://public-api.dapi.digital/docs/reference/products.md#variant_object).

### Responses

-   `200`: The updated variant.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no variant with this id (`variant_not_found`).
-   `409`: The SKU is already used (`sku_already_used`), or the variant was imported from a sales channel that owns its SKU and price (`variant_managed_by_channel`).
-   `422`: `option_mismatch`, `material_not_allowed`, `material_not_found`, `extra_not_found`, `extra_not_approved` (a rejected extra), `stock_not_found`, `stock_not_approved`, `category_not_found`, `variant_needs_design_or_stock`, `price_below_minimum` (minimum in `details.minimum`), `currency_not_supported` or `design_not_in_product`. The price floor is only checked when the price or the contents change.
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X PATCH "https://public-api.dapi.digital/v2/variants/var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "price": {
    "amount": 5500,
    "currency": "usd"
  },
  "status": "active"
}'
```

## Delete a variant

DELETE `/v2/variants/{id}`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `delete_variant`

Removes a variant, as the dashboard does: kept and marked deleted when orders reference it (so order history stays intact), removed otherwise. Either way it stops being sold and no longer appears on the product. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the variant, prefixed with `var_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The variant was deleted.

Response attributes (object):

-   `id` (string): The id of the deleted variant.
-   `object` (string): Always `variant`.
-   `deleted` (boolean): Always `true`: the deletion succeeded.

### Responses

-   `200`: The variant was deleted.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no variant with this id (`variant_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X DELETE "https://public-api.dapi.digital/v2/variants/var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "variant",
  "deleted": true
}
```

## The product compliance object

The EU/UK product safety documents a product needs, what you uploaded for each, and whether they are approved.

### Attributes

-   `object` (string): Names the type of this object. Always `product_compliance`.
-   `product` (ProductReference or null): Reference to a product. Pass the matching `expand[]` path to receive the full product instead.
    -   `id` (string): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `product`.
    -   `name` (string): Display name of the product.
-   `enforced` (boolean): Whether missing documents block publishing this product right now. Only EU/UK sellers are held to it, and only while document enforcement is switched on by 3DAPI; when true, the product `publishing.blockers` include `compliance_documents_missing` until `complete` is true.
-   `complete` (boolean): True when every requirement is approved.
-   `requirements` (array of ComplianceRequirement): Every document the product categories require, in category order.
    -   `key` (string): Stable identifier of the requirement, as listed on the category.
    -   `name` (string): Name of the required document, as the category lists it.
    -   `status` (ComplianceRequirementStatus):
        
        Where a requirement stands:
        
        -   `approved`: an approved document covers it.
        -   `pending_review`: a document is uploaded and waiting for review by 3DAPI.
        -   `rejected`: every document uploaded for it was rejected; see each `review.reason`.
        -   `missing`: nothing is uploaded for it yet.
        
    -   `required_by` (array of CategoryReference): The categories that ask for this document: the product category and any variant category overrides. Same shape as `CategoryReference` above.
    -   `documents` (array of ComplianceDocument): Documents uploaded for this requirement, newest first.
        -   `id` (string): Unique identifier of the compliance document, prefixed with `cdc_`. Treat it as an opaque string: store it and compare it, never parse it.
        -   `object` (string): Names the type of this object. Always `compliance_document`.
        -   `name` (string): File name.
        -   `format` (string or null): File extension, lowercase.
        -   `size_bytes` (integer or null): File size in bytes.
        -   `url` (string): Signed URL to download the document. Valid for 15 minutes; get the compliance view again for a fresh one.
        -   `review` (Review): Review by 3DAPI. Anything that will be manufactured is reviewed before it can be sold or produced.
            -   `status` (ReviewStatus): Same shape as `ReviewStatus` above.
            -   `reason` (string or null): Why it was rejected. Null unless `status` is `rejected`.
            -   `reviewed_at` (timestamp or null): When the latest review decision was made. Null while pending.
        -   `uploaded_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `other_documents` (array of ComplianceDocument): Documents uploaded for requirements that no longer apply, for example after a category change. Kept so nothing you uploaded disappears. Same shape as `ComplianceDocument` above.

**The product compliance object**

```json
{
  "object": "product_compliance",
  "product": {
    "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "product",
    "name": "Action Figure"
  },
  "enforced": false,
  "complete": false,
  "requirements": [
    {
      "key": "material-toxicity-assessment",
      "name": "Material Toxicity Assessment",
      "status": "approved",
      "required_by": [
        {
          "id": "cat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "category",
          "name": "Action Figures & Dolls"
        }
      ],
      "documents": [
        {
          "id": "cdc_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "compliance_document",
          "name": "toxicity-report.pdf",
          "format": "pdf",
          "size_bytes": 182344,
          "url": "https://files.example.com/compliance/toxicity-report.pdf?signature=abc",
          "review": {
            "status": "approved",
            "reason": null,
            "reviewed_at": "2026-10-02T10:00:00Z"
          },
          "uploaded_at": "2026-10-01T08:00:00Z"
        }
      ]
    },
    {
      "key": "technical-file-and-risk-assessment",
      "name": "Technical File & Risk Assessment",
      "status": "missing",
      "required_by": [
        {
          "id": "cat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "category",
          "name": "Action Figures & Dolls"
        }
      ],
      "documents": []
    }
  ],
  "other_documents": []
}
```

## Upload a compliance document

POST `/v2/products/{id}/compliance/documents`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `upload_compliance_document`

Adds a document for one requirement of the product. It is pending review by 3DAPI, and the requirement shows `pending_review` until then. To replace a document, upload the new one and delete the old one. Returns the whole compliance view. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Headers

-   `Idempotency-Key` (string): Any unique string, for example a UUID. Sending the same request again with the same key within 24 hours returns the first response without doing anything twice. Reusing a key with a different body is refused with `idempotency_key_reused`. A refused request (4xx) frees the key so you can correct and resend it; a server error (5xx) is kept and returned again, because the write may already have happened: check the resource before retrying with a new key.

### Request body

-   `requirement` (string, required): The `key` of the requirement this document covers, from the product compliance view or the category.
-   `file` (object, required): The document, uploaded first with purpose `compliance_document`.
    -   `upload` (string, required): An upload id from `POST /v2/uploads`, used before it expires. The file is checked when used: one that is not what its name and type say is refused with 422 `invalid_file` (the expected type in `details.expected`) and must be uploaded again.

### Returns 201 Created

The compliance view with the new document. Returns [the product compliance object](https://public-api.dapi.digital/docs/reference/products.md#product_compliance_object).

### Responses

-   `201`: The compliance view with the new document.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no product with this id (`product_not_found`).
-   `422`: The requirement does not apply to this product (`unknown_requirement`); `details.requirements` lists the valid keys.
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/compliance/documents" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "requirement": "material-toxicity-assessment",
  "file": {
    "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
  }
}'
```

## Delete a compliance document

DELETE `/v2/compliance-documents/{id}`

- **Credentials**: An API key with the `products:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `delete_compliance_document`

Removes a document you uploaded. Returns the compliance view of its product, so you see which requirements it leaves missing. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the compliance document, prefixed with `cdc_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The compliance view without the document. Returns [the product compliance object](https://public-api.dapi.digital/docs/reference/products.md#product_compliance_object).

### Responses

-   `200`: The compliance view without the document.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no compliance document with this id (`compliance_document_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X DELETE "https://public-api.dapi.digital/v2/compliance-documents/cdc_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

## Get product compliance

GET `/v2/products/{id}/compliance`

- **Credentials**: An API key with the `products:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers

- **Operation id**: `get_product_compliance`

Returns every safety document the product categories require, the documents you uploaded for each and their review status. Use it to see what is still missing before publishing to EU/UK buyers. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the product, prefixed with `prd_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The compliance view. Returns [the product compliance object](https://public-api.dapi.digital/docs/reference/products.md#product_compliance_object).

### Responses

-   `200`: The compliance view.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no product with this id (`product_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/products/prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/compliance" \
  -H "x-api-key: $DAPI_API_KEY"
```

---

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