# Orders

Orders for your products, from every channel.

This page covers [the order object](https://public-api.dapi.digital/docs/reference/orders.md#order_object) and [the import issue object](https://public-api.dapi.digital/docs/reference/orders.md#import_issue_object).

## Endpoints

-   [GET `/v2/orders`](https://public-api.dapi.digital/docs/reference/orders.md#list_orders)
-   [POST `/v2/orders`](https://public-api.dapi.digital/docs/reference/orders.md#create_order)
-   [POST `/v2/orders/{id}/cancel`](https://public-api.dapi.digital/docs/reference/orders.md#cancel_order)
-   [GET `/v2/orders/{id}`](https://public-api.dapi.digital/docs/reference/orders.md#get_order)
-   [GET `/v2/import-issues`](https://public-api.dapi.digital/docs/reference/orders.md#list_import_issues)
-   [GET `/v2/import-issues/{id}`](https://public-api.dapi.digital/docs/reference/orders.md#get_import_issue)

## The order object

An order for your products, from any channel: what was ordered, where it ships, its status, what was charged and what you earn.

### Attributes

-   `id` (string): Unique identifier of the order, prefixed with `ord_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `order`.
-   `number` (string): Human-readable order number, the one your customers and 3DAPI support use.
-   `channel` (OrderChannel):
    
    Where the order came from:
    
    -   `storefront`: a buyer checked out on your 3DAPI store and paid by card.
    -   `shopify`: imported from your Shopify store.
    -   `shipstation`: imported from ShipStation.
    -   `api`: created through the API.
    -   `manual`: placed by you from the dashboard, charged at cost.
    -   `replacement`: a reprint after a quality issue; `charges.paid_by` says who paid for it.
    
-   `status` (OrderStatus):
    
    Where the order is:
    
    -   `awaiting_payment`: you still have to pay for it.
    -   `paid`: paid, waiting to be assigned to a production partner.
    -   `assigned`: a production partner has it; production has not started.
    -   `in_production`: being made.
    -   `shipped`: on its way to the customer. `shipping` has the tracking details.
    -   `completed`: delivered and settled.
    -   `on_hold`: there is not enough stock to fulfil it.
    -   `rejected`: the production partner refused it.
    -   `returned`: sent back after shipping.
    -   `cancelled`: cancelled, usually after a full refund.
    
-   `customer` (OrderCustomer or null): Who ordered. Personal data: null unless the API key has the `orders:read_customer` scope.
    -   `name` (string): Customer full name.
    -   `email` (string or null): Customer email.
    -   `phone` (string or null): Customer phone.
    -   `company` (object or null): Set when the buyer bought as a business.
        -   `name` (string or null): Company name.
        -   `tax_id` (string or null): VAT or tax number the buyer entered.
        -   `tax_id_validated` (boolean or null): True when VIES confirmed the EU VAT number, so the sale was zero-rated. False when it did not. Null when not applicable.
-   `shipping_address` (OrderShippingAddress): Where the order ships. The country is always shown because it decides shipping and VAT; the rest is personal data.
    -   `formatted` (string or null): The full address as one line. Null without the `orders:read_customer` scope.
    -   `city` (string or null): City. Null without the `orders:read_customer` scope.
    -   `country` (string or null): Country name. Always present when known.
    -   `country_code` (string or null): ISO 3166-1 alpha-2 country code. Always present when known.
-   `charges` (OrderCharges): What the payer was charged for the order.
    -   `paid_by` (string): `buyer` when a customer paid 3DAPI (storefront orders), `you` when you paid for the order (manual, imported and API orders, and replacements you were responsible for), `dapi` when 3DAPI covers it (a replacement for a production partner mistake; you were not charged, and the charges show what the replacement cost). `earnings` is null unless `buyer`.
    -   `items` (object): Items total.
        -   `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): Shipping charged.
        -   `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.
    -   `vat` (object): VAT on the order. Zero outside the EU and UK.
        -   `amount` (object): VAT charged.
            -   `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.
        -   `rate` (number): VAT rate as a fraction, for example 0.21. 0 when none.
            
            Range 0 to 1.
            
        -   `included` (boolean): True when VAT is carved out of the prices; false when it was added on top.
        -   `country` (string or null): Country whose rate applied, ISO alpha-2. Null when none.
    -   `sample_credit` (object or null): Sample credit that covered part of the charge. Null when none was used.
        -   `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.
    -   `total` (object): The full charge, VAT included, before any sample credit.
        -   `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` (OrderEarnings or null): What you earn from a buyer-paid order and what was taken off it. Null when you paid for the order yourself.
    -   `manufacturing` (object): Paid to the production partner for this order. 0 on an order fulfilled from prepaid stock: that manufacturing was paid when you bought the stock batch.
        -   `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.
    -   `payment_processing` (object): Card processing fee taken from your earnings on the buyer payment. Kept by the card processor even when the order is refunded. 0 on older orders whose payout was made without deducting it.
        -   `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.
    -   `refunded` (object): What completed refunds took off your earnings.
        -   `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.
    -   `net` (object): What you keep. Never rounded up to 0: minimum prices include the card fee, so it is not negative on sales made at or above them, but a price set before that rule can show a negative amount here.
        -   `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` (OrderShipping): Shipping details.
    -   `tier` (string or null): Shipping tier the order was priced at, frozen at placement: `small`, `medium` or `large`, the weight bands a variant's `shipping.tier` describes. Null when it was priced at the flat rate.
    -   `carrier` (string or null): Carrier, once shipped.
    -   `tracking_number` (string or null): Tracking number, once shipped.
    -   `shipped_at` (timestamp or null): When the order shipped. Null before shipping, and for some orders that shipped before this was recorded.
-   `external` (object or null): The original order in the sales channel, for Shopify and ShipStation orders. Null otherwise.
    -   `channel_order_id` (string or null): The order id in Shopify or ShipStation.
    -   `channel_total` (object or null): What the customer paid in that channel, when known.
        -   `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.
-   `customer_notes` (string or null): Notes the customer left at checkout. Null without the `orders:read_customer` scope, since buyers often write personal details here.
-   `qc` (object): Quality control on this order. List QC tickets with `order` for the details.
    -   `open_tickets` (integer): QC tickets on this order that are not approved, closed or cancelled.
        
        Range at least 0.
        
    -   `blocks_shipping` (boolean): True when an open quality check holds the order back from shipping.
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `updated_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `item_count` (integer): Units ordered across all lines.
    
    Range at least 0.
    
-   `items` (array of OrderItem): The order lines.
    -   `id` (string): Unique identifier of the order item, prefixed with `itm_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `order_item`.
    -   `product` (object or null): The product, as named when ordered.
        -   `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.
    -   `variant` (object): The variant ordered, as named when ordered.
        -   `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`.
        -   `name` (string): Display name of the variant.
    -   `sku` (string or null): The variant SKU at the time of the order.
    -   `quantity` (integer): Units ordered on this line.
        
        Range at least 1.
        
    -   `unit_price` (object): What the payer paid per unit: the sales price when a buyer paid, manufacturing plus platform fee when you paid.
        -   `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.
    -   `total` (object): `unit_price` times `quantity`.
        -   `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.
    -   `stock_used` (array of object): The stock batches this line took units from, and how many. Empty when nothing came from stock.
        -   `batch` (StockBatchReference): Reference to a stock batch. Pass the matching `expand[]` path to receive the full stock batch instead.
            -   `id` (string): Unique identifier of the stock batch, prefixed with `bat_`. Treat it as an opaque string: store it and compare it, never parse it.
            -   `object` (string): Names the type of this object. Always `stock_batch`.
            -   `name` (string): Display name of the stock batch.
        -   `units` (integer): Units this line took from that batch.
            
            Range at least 1.
            
    -   `custom_file` (object or null): The custom file printed on this line instead of the design, when the order sent one. Null otherwise.
        -   `name` (string): File name.
        -   `size_bytes` (integer): File size in bytes.
            
            Range at least 0.
            
    -   `fulfilled_from_stock` (boolean): True when at least part of this line ships from your ready-made stock instead of being printed. Its manufacturing was paid when you bought the stock batch, so it is not charged again on this order; an order fulfilled from prepaid stock shows `earnings.manufacturing` as 0.
-   `refunds` (array of OrderRefund): Completed refunds, oldest first. Empty when none.
    -   `id` (string): Unique identifier of the refund, prefixed with `rfd_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `amount` (object): Total refunded, VAT included.
        -   `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.
    -   `created_at` (timestamp): Point in time, ISO 8601 in UTC.

**The order object**

```json
{
  "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "order",
  "number": "PV-SCN-1701A372",
  "channel": "storefront",
  "status": "in_production",
  "customer": {
    "name": "Ana Popescu",
    "email": "ana@example.com",
    "phone": null,
    "company": null
  },
  "shipping_address": {
    "formatted": "Str. Test 1, Cluj, RO",
    "city": "Cluj",
    "country": "Romania",
    "country_code": "RO"
  },
  "charges": {
    "paid_by": "buyer",
    "items": {
      "amount": 1800,
      "currency": "usd"
    },
    "shipping": {
      "amount": 600,
      "currency": "usd"
    },
    "vat": {
      "amount": {
        "amount": 0,
        "currency": "usd"
      },
      "rate": 0,
      "included": false,
      "country": null
    },
    "sample_credit": null,
    "total": {
      "amount": 2400,
      "currency": "usd"
    }
  },
  "earnings": {
    "manufacturing": {
      "amount": 900,
      "currency": "usd"
    },
    "platform_fee": {
      "amount": 300,
      "currency": "usd"
    },
    "payment_processing": {
      "amount": 102,
      "currency": "usd"
    },
    "refunded": {
      "amount": 0,
      "currency": "usd"
    },
    "net": {
      "amount": 498,
      "currency": "usd"
    }
  },
  "shipping": {
    "tier": "small",
    "carrier": null,
    "tracking_number": null,
    "shipped_at": null
  },
  "external": null,
  "customer_notes": null,
  "qc": {
    "open_tickets": 1,
    "blocks_shipping": true
  },
  "created_at": "2026-10-09T08:39:29Z",
  "updated_at": "2026-10-09T08:40:02Z",
  "item_count": 1,
  "items": [
    {
      "id": "itm_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "order_item",
      "product": {
        "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "product",
        "name": "Clicker"
      },
      "variant": {
        "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "variant",
        "name": "Clicker Blue"
      },
      "sku": "CLICK-BLUE",
      "quantity": 1,
      "unit_price": {
        "amount": 1800,
        "currency": "usd"
      },
      "total": {
        "amount": 1800,
        "currency": "usd"
      },
      "stock_used": [],
      "custom_file": null,
      "fulfilled_from_stock": false
    }
  ],
  "refunds": []
}
```

## List orders

GET `/v2/orders`

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

- **For**: Designers

- **Operation id**: `list_orders`

Lists orders for your products from every channel, newest first. Items and refunds are left out to stay small; get the order for them, or pass `expand[]=items`. Customer details need the `orders:read_customer` scope. Designers only.

### Query parameters

-   `status` (array of OrderStatus): Only orders in these states. Repeat for several, for example `status=paid&status=assigned`. Same shape as `OrderStatus` above.
-   `channel` (array of OrderChannel): Only orders from these channels. Same shape as `OrderChannel` above.
-   `search` (string): Matches the order number, or the customer name or email when the key has `orders:read_customer`.
-   `reference` (string): Exact match on your own order id, the `reference` you sent with create\_order. Use it to check whether an order from another platform was already placed.
-   `created_after` (timestamp): Only orders placed after this time.
-   `created_before` (timestamp): Only orders placed before this time.
-   `updated_after` (timestamp): Only orders changed after this time. Use it to sync status changes since your last poll.
-   `sort` (string): Order of results. A leading `-` means descending.
    
    Possible values: `-created_at`, `created_at`, `-updated_at`.
    
    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[]=items`. Unknown paths are rejected with `invalid_expand`.

### Returns 200 OK

A page of orders.

Response attributes (OrderList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of OrderSummary): The page of results.
    -   `id` (string): Unique identifier of the order, prefixed with `ord_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `order`.
    -   `number` (string): Human-readable order number, the one your customers and 3DAPI support use.
    -   `channel` (OrderChannel): Same shape as `OrderChannel` above.
    -   `status` (OrderStatus): Same shape as `OrderStatus` above.
    -   `customer` (OrderCustomer or null): Same shape as `OrderCustomer` above.
    -   `shipping_address` (OrderShippingAddress): Same shape as `OrderShippingAddress` above.
    -   `charges` (OrderCharges): Same shape as `OrderCharges` above.
    -   `earnings` (OrderEarnings or null): Same shape as `OrderEarnings` above.
    -   `shipping` (OrderShipping): Same shape as `OrderShipping` above.
    -   `external` (object or null): The original order in the sales channel, for Shopify and ShipStation orders. Null otherwise.
        -   `channel_order_id` (string or null): The order id in Shopify or ShipStation.
        -   `channel_total` (object or null): What the customer paid in that channel, when known.
            -   `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.
    -   `customer_notes` (string or null): Notes the customer left at checkout. Null without the `orders:read_customer` scope, since buyers often write personal details here.
    -   `qc` (object): Quality control on this order. List QC tickets with `order` for the details.
        -   `open_tickets` (integer): QC tickets on this order that are not approved, closed or cancelled.
            
            Range at least 0.
            
        -   `blocks_shipping` (boolean): True when an open quality check holds the order back from shipping.
    -   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
    -   `updated_at` (timestamp): Point in time, ISO 8601 in UTC.
    -   `item_count` (integer): Units ordered across all lines.
        
        Range at least 0.
        
    -   `items` (array of OrderItem, optional): Only present with `expand[]=items`. Same shape as `OrderItem` above.
-   `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 orders.
-   `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/orders" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "order",
      "number": "PV-SCN-1701A372",
      "channel": "storefront",
      "status": "in_production",
      "customer": {
        "name": "Ana Popescu",
        "email": "ana@example.com",
        "phone": null,
        "company": null
      },
      "shipping_address": {
        "formatted": "Str. Test 1, Cluj, RO",
        "city": "Cluj",
        "country": "Romania",
        "country_code": "RO"
      },
      "charges": {
        "paid_by": "buyer",
        "items": {
          "amount": 1800,
          "currency": "usd"
        },
        "shipping": {
          "amount": 600,
          "currency": "usd"
        },
        "vat": {
          "amount": {
            "amount": 0,
            "currency": "usd"
          },
          "rate": 0,
          "included": false,
          "country": null
        },
        "sample_credit": null,
        "total": {
          "amount": 2400,
          "currency": "usd"
        }
      },
      "earnings": {
        "manufacturing": {
          "amount": 900,
          "currency": "usd"
        },
        "platform_fee": {
          "amount": 300,
          "currency": "usd"
        },
        "payment_processing": {
          "amount": 102,
          "currency": "usd"
        },
        "refunded": {
          "amount": 0,
          "currency": "usd"
        },
        "net": {
          "amount": 498,
          "currency": "usd"
        }
      },
      "shipping": {
        "tier": "small",
        "carrier": null,
        "tracking_number": null,
        "shipped_at": null
      },
      "external": null,
      "customer_notes": null,
      "qc": {
        "open_tickets": 1,
        "blocks_shipping": true
      },
      "created_at": "2026-10-09T08:39:29Z",
      "updated_at": "2026-10-09T08:40:02Z",
      "item_count": 1
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Create an order

POST `/v2/orders`

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

- **For**: Designers

- **Operation id**: `create_order`

Orders your own variants for a customer, for example a sale on your own website. You pay at cost (production price, shipping and VAT) from your wallet; if the balance is short, the difference is charged to your saved card. The order comes back paid, numbered `2AP...`, with `channel: api`, and goes to production. If payment fails, no order is created. With custom files turned on for your account, a line can print your own .3mf or .stl instead of the design of the variant (`items[].file`, for example a keychain with a different name for every customer); see the Order with a custom file recipe. 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

-   `items` (array of object, required): What to produce and ship: 1 to 100 lines of 1 to 1000 units each.
    -   `variant` (string, required): One of your variants. Its product must be approved.
    -   `quantity` (integer, required): Units of this variant to produce and ship, 1 to 1000.
        
        Range 1 to 1000.
        
    -   `file` (object or null): A custom file printed on this line instead of the variant's design, for example a keychain with a different name each time: an upload with purpose `order_file` (.3mf or .stl). Custom files are turned on per account by 3DAPI: ask [contact@dapi.digital](mailto:contact@dapi.digital) (`custom_files_not_enabled`, 403, otherwise). The variant must be a single design. The file is not reviewed or re-sliced: the line is charged the variant's production price, so it should weigh about the same as the design it replaces. Omit or null to print the variant as it is.
        -   `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.
-   `customer` (object, required): Who receives the order.
    -   `name` (string, required): Full name of the person receiving the order.
    -   `email` (string, required): Email of the person receiving the order.
    -   `phone` (string or null): Phone number with country prefix, for the carrier.
-   `shipping_address` (object, required): Where to ship. Stored as one formatted line plus the country, which the order returns as `shipping_address.formatted`.
    -   `line1` (string, required): Street and number.
    -   `line2` (string or null): Apartment, suite, floor or building. Omit or send null when there is none.
    -   `city` (string, required): City or town.
    -   `state` (string or null): State, province, county or region, where the address uses one. Omit or send null otherwise.
    -   `postal_code` (string or null): Postal or ZIP code. Omit or send null where the country has none.
    -   `country_code` (string, required): ISO 3166-1 alpha-2, uppercase. A code that is not a real country is refused with `validation_failed`.
-   `reference` (string or null): Your own order id, for matching. Letters, digits, `-` and `_`. Returned as `external.channel_order_id`; find the order again with `list_orders?reference=`. Unique among your orders that are not cancelled: a second order with the same reference is refused with `reference_in_use` (409, the existing order in `details.order`) and nothing is charged, so retrying an order from another platform never places it twice.
-   `customer_notes` (string or null): Free-text notes for the order, up to 2000 characters. Returned as `customer_notes`, which is only shown to keys with the `orders:read_customer` scope.
-   `discount_code` (string or null): A discount code from 3DAPI.
-   `use_sample_credit` (boolean): Spend your sample credit on this order first. How much applies is decided by 3DAPI and shown in `charges.sample_credit`.
    
    Defaults to false.
    

### Returns 201 Created

The order, paid. Returns [the order object](https://public-api.dapi.digital/docs/reference/orders.md#order_object).

### Responses

-   `201`: The order, paid.
-   `400`: The request is invalid (`validation_failed`), or a discount code is unknown (`invalid_discount_code`).
-   `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`).
-   `402`: Payment failed: no saved card to cover the shortfall, or the card was declined (`payment_failed`). No order was created.
-   `403`: A line sends a `file`, but custom files are not turned on for your account (`custom_files_not_enabled`); ask [contact@dapi.digital](mailto:contact@dapi.digital) to turn them on.
-   `409`: Another of your orders that is not cancelled already has this `reference` (`reference_in_use`; `details.order` is that order, `{ id, number }`), or production is paused (`production_unavailable`). Nothing was placed or charged.
-   `422`: A variant is not orderable: unknown, inactive, its product is not approved, or a line with a `file` names a variant made of several designs (`variant_not_orderable`); the file upload is unknown, expired, used or of another purpose (`unknown_upload`, `upload_expired`, `upload_already_used`, `upload_purpose_mismatch`, `upload_missing`), or its contents are not a .3mf or .stl (`invalid_file`); or 3DAPI does not deliver to the country (`shipping_destination_not_served`: Russia, Belarus, Iran or Syria).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user), plus 60 per minute per API key (or signed-in user) for quote, upload, order and payment operations. 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/orders" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "variant": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "quantity": 2
    }
  ],
  "customer": {
    "name": "Ana Popescu",
    "email": "ana@example.com",
    "phone": "+40712345678"
  },
  "shipping_address": {
    "line1": "Str. Test 1",
    "city": "Cluj-Napoca",
    "postal_code": "400001",
    "country_code": "RO"
  },
  "reference": "SHOP-10023"
}'
```

## Cancel an order

POST `/v2/orders/{id}/cancel`

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

- **For**: Designers

- **Operation id**: `cancel_order`

Cancels one of your orders while it is still `paid` (no production partner has started) and refunds it in full to where it was paid from. Wallet-paid orders come back `cancelled` at once; card-paid orders stay `paid` until the card refund completes, then become `cancelled` and `order.cancelled` is sent. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the order, prefixed with `ord_`. 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 order, cancelled or with its refund on the way. Returns [the order object](https://public-api.dapi.digital/docs/reference/orders.md#order_object).

### Responses

-   `200`: The order, cancelled or with its refund on the way.
-   `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 order with this id (`order_not_found`).
-   `409`: The order cannot be cancelled here (`order_not_cancellable`): it is past `paid` (contact support), a refund on it is already on the way, it is not an order you paid for yourself (API and manual orders only), it was created through API v1 (cancel those from the dashboard or contact support), it was paid by card and card refunds are unavailable, or the card refund was refused. The message says which; the order and your money are unchanged.
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user), plus 60 per minute per API key (or signed-in user) for quote, upload, order and payment operations. 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/orders/ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/cancel" \
  -H "x-api-key: $DAPI_API_KEY"
```

## Get an order

GET `/v2/orders/{id}`

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

- **For**: Designers

- **Operation id**: `get_order`

Returns one order with its items, charges, earnings, shipping and refunds. Customer details need the `orders:read_customer` scope. Designers only.

### Path parameters

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

### Returns 200 OK

The order. Returns [the order object](https://public-api.dapi.digital/docs/reference/orders.md#order_object).

### Responses

-   `200`: The order.
-   `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 order with this id (`order_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/orders/ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

## The import issue object

An order from a connected sales channel that could not be imported, and why. Retry or dismiss it from the dashboard; once the order imports, the issue disappears from this list.

### Attributes

-   `id` (string): Unique identifier of the import issue, prefixed with `imp_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `import_issue`.
-   `channel` (ImportChannel): The sales channel the order came from. Only `shipstation` today; `shopify` joins when the Shopify integration ships.
-   `channel_order_id` (string): The order id in the sales channel.
-   `channel_order_number` (string or null): The order number your customer sees in the sales channel.
-   `ordered_at` (timestamp or null): When the customer placed the order in the sales channel.
-   `reason` (ImportIssueReason):
    
    Why the order could not be imported:
    
    -   `unknown_sku`: at least one item has a SKU that matches none of your variants. Give a variant that SKU, then import the order again.
    -   `no_items`: the order arrived with no items to make, or only lines such as shipping or discounts. Add the products in the sales channel, then import it again.
    -   `variant_not_found`: an item's SKU is linked to a variant that no longer exists. Link the SKU to a current variant, then import the order again.
    -   `multiple_sellers`: the order's SKUs are linked to products of more than one seller, and an order is made for one seller only. Link every SKU to your own variants or split the order, then import it again.
    
-   `message` (string): What went wrong and how to fix it, ready to show to a person.
-   `unmatched_items` (array of object): The items that matched none of your variants. Filled for `unknown_sku` only; empty for the other reasons.
    -   `sku` (string or null): The SKU as it arrived from the channel.
    -   `name` (string or null): The item name in the channel.
    -   `quantity` (integer): Units of this item in the channel order.
        
        Range at least 1.
        
-   `created_at` (timestamp): When the import failed.

**The import issue object**

```json
{
  "id": "imp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "import_issue",
  "channel": "shipstation",
  "channel_order_id": "193554871",
  "channel_order_number": "3002",
  "ordered_at": "2026-10-07T14:20:54Z",
  "reason": "unknown_sku",
  "message": "1 item has a SKU that matches none of your variants. Give the variant this SKU, then import the order again.",
  "unmatched_items": [
    {
      "sku": "PR-TRACKERV2-465KG",
      "name": "PR Tracker V2 - 465 Kilograms (kg) Plates",
      "quantity": 1
    }
  ],
  "created_at": "2026-10-07T14:21:10Z"
}
```

## List import issues

GET `/v2/import-issues`

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

- **For**: Designers

- **Operation id**: `list_import_issues`

Lists orders from your connected sales channels that could not be imported, newest first, with what to fix. Filter on `sku` to find every order one missing SKU mapping blocks. Designers only.

### Query parameters

-   `channel` (string): Only this channel.
-   `reason` (string): Only this reason.
    
    Possible values: `unknown_sku`, `no_items`, `variant_not_found`, `multiple_sellers`.
    
-   `sku` (string): Only issues with an unmatched item of exactly this SKU. Use it to find every order one missing mapping is blocking.
-   `ordered_after` (timestamp): Only orders placed after this time.
-   `ordered_before` (timestamp): Only orders placed before this time.
-   `sort` (string): Order of results. A leading `-` means newest first.
    
    Possible values: `-ordered_at`, `ordered_at`, `-created_at`.
    
    Defaults to "-ordered\_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.

### Returns 200 OK

A page of import issues.

Response attributes (ImportIssueList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of ImportIssue): The page of results.
    
    Described in [the import issue object](https://public-api.dapi.digital/docs/reference/orders.md#import_issue_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 import issues.
-   `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/import-issues" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "imp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "import_issue",
      "channel": "shipstation",
      "channel_order_id": "193554871",
      "channel_order_number": "3002",
      "ordered_at": "2026-10-07T14:20:54Z",
      "reason": "unknown_sku",
      "message": "1 item has a SKU that matches none of your variants. Give the variant this SKU, then import the order again.",
      "unmatched_items": [
        {
          "sku": "PR-TRACKERV2-465KG",
          "name": "PR Tracker V2 - 465 Kilograms (kg) Plates",
          "quantity": 1
        }
      ],
      "created_at": "2026-10-07T14:21:10Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Get an import issue

GET `/v2/import-issues/{id}`

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

- **For**: Designers

- **Operation id**: `get_import_issue`

Returns one order that could not be imported. Designers only.

### Path parameters

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

### Returns 200 OK

The import issue. Returns [the import issue object](https://public-api.dapi.digital/docs/reference/orders.md#import_issue_object).

### Responses

-   `200`: The import issue.
-   `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 import issue with this id (`import_issue_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/import-issues/imp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

---

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