# Stocks

Ready-made units bought in batches.

This page covers [the stock object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_object), [the stock batch object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_batch_object), [the stock purchase object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_purchase_object) and [the region object](https://public-api.dapi.digital/docs/reference/stocks.md#region_object).

## Endpoints

-   [GET `/v2/stocks`](https://public-api.dapi.digital/docs/reference/stocks.md#list_stocks)
-   [POST `/v2/stocks`](https://public-api.dapi.digital/docs/reference/stocks.md#create_stock)
-   [GET `/v2/stocks/{id}`](https://public-api.dapi.digital/docs/reference/stocks.md#get_stock)
-   [PATCH `/v2/stocks/{id}`](https://public-api.dapi.digital/docs/reference/stocks.md#update_stock)
-   [POST `/v2/stocks/{id}/designs`](https://public-api.dapi.digital/docs/reference/stocks.md#add_stock_design)
-   [PUT `/v2/stocks/{id}/restock/{region_id}`](https://public-api.dapi.digital/docs/reference/stocks.md#set_stock_restock)
-   [GET `/v2/stock-batches`](https://public-api.dapi.digital/docs/reference/stocks.md#list_all_stock_batches)
-   [GET `/v2/stocks/{id}/batches/{batch_id}/usage`](https://public-api.dapi.digital/docs/reference/stocks.md#list_stock_batch_usage)
-   [GET `/v2/stocks/{id}/batches`](https://public-api.dapi.digital/docs/reference/stocks.md#list_stock_batches)
-   [GET `/v2/stocks/{id}/batches/{batch_id}`](https://public-api.dapi.digital/docs/reference/stocks.md#get_stock_batch)
-   [POST `/v2/stock-purchases`](https://public-api.dapi.digital/docs/reference/stocks.md#create_stock_purchase)
-   [GET `/v2/regions/{id}`](https://public-api.dapi.digital/docs/reference/stocks.md#get_region)

## The stock object

Ready-made units you buy in batches ahead of time and that are kept in storage until orders use them. A variant can ship from a stock instead of being printed on demand.

### Attributes

-   `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.
-   `description` (string or null): Longer description, when provided.
-   `review` (Review): Review by 3DAPI. Anything that will be manufactured is reviewed before it can be sold or produced.
    -   `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`.
    -   `reviewed_at` (timestamp or null): When the latest review decision was made. Null while pending.
-   `units_per_batch` (integer): How many units one purchased batch contains.
    
    Range at least 0.
    
-   `batch_price` (object or null): What you pay for one batch, the same in every region. Set by 3DAPI when the stock is approved; null until then.
    -   `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.
-   `unit_price` (object or null): What one unit costs you: `batch_price` divided by `units_per_batch`. Null until the stock 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.
-   `contents` (array of objects): What every unit of the stock is made of.
    -   Shape **type: design**:
        
        -   `type` (string): A printed part.
        -   `design` (object): A design reference by default. With `expand[]=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).
                
    -   Shape **type: extra**:
        
        -   `type` (string): A bought-in part.
        -   `extra` (object): A extra reference by default. With `expand[]=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).
                
-   `inventory` (object): Units across every batch you bought.
    -   `available` (integer): Units made and ready to ship.
        
        Range at least 0.
        
    -   `in_production` (integer): Units paid for and not yet made.
        
        Range at least 0.
        
    -   `by_region` (array of object): The same counts per production region. Orders are filled from a region that ships to the buyer.
        -   `region` (RegionReference): Reference to a region. Pass the matching `expand[]` path to receive the full region instead.
            -   `id` (string): Unique identifier of the region, prefixed with `reg_`. Treat it as an opaque string: store it and compare it, never parse it.
            -   `object` (string): Names the type of this object. Always `region`.
            -   `name` (string): Display name of the region.
        -   `available` (integer): Units made and ready to ship.
            
            Range at least 0.
            
        -   `in_production` (integer): Units paid for and not yet made.
            
            Range at least 0.
            
-   `restock` (array of object): Restock rule for each region the stock can be produced in. The regions are chosen by 3DAPI at approval.
    -   `region` (RegionReference): Same shape as `RegionReference` above.
    -   `mode` (string):
        
        What happens when this region reaches `when_available_below`:
        
        -   `off`: nothing.
        -   `notify`: you get a low-stock email.
        -   `auto_buy`: batches for `reorder_units` are bought from your wallet, when auto-reorder is enabled on your account and the balance covers them; otherwise you get the email.
        
    -   `when_available_below` (integer or null): Threshold in units. The rule is checked each time an order draws units from this stock (when the order is assigned for production), not continuously. It counts the units paid for minus the units used in this region, across every batch and including units still in production (so not `available`), and triggers when that count is at or below this threshold. Null when `mode` is `off`.
        
        Range at least 1.
        
    -   `reorder_units` (integer or null): Units to buy per automatic purchase, returned as set. Purchases round up to whole batches: an automatic purchase buys `reorder_units` divided by `units_per_batch`, rounded up (at least one batch), at `batch_price` each, and the batch it creates holds exactly those batches times `units_per_batch` units. A multiple of `units_per_batch` buys exactly what you ask for. Only set when `mode` is `auto_buy`.
        
        Range at least 1.
        
-   `images` (array of Image): Photos of the stock.
    -   `url` (string): Public image URL.
    -   `primary` (boolean): True for the main image, shown first. At most one.
-   `attachments` (array of objects): Production documents for this stock.
    -   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).
        
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `updated_at` (timestamp): Point in time, ISO 8601 in UTC.

**The stock object**

```json
{
  "id": "stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "stock",
  "name": "Clicker 50 pack",
  "description": "Pre-assembled clickers, bagged.",
  "review": {
    "status": "approved",
    "reason": null,
    "reviewed_at": "2026-05-02T10:00:00Z"
  },
  "units_per_batch": 50,
  "batch_price": {
    "amount": 30000,
    "currency": "usd"
  },
  "unit_price": {
    "amount": 600,
    "currency": "usd"
  },
  "contents": [
    {
      "type": "design",
      "design": {
        "id": "dsg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "design",
        "name": "clicker.3mf"
      }
    },
    {
      "type": "extra",
      "extra": {
        "id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "extra",
        "name": "M3 screw"
      }
    }
  ],
  "inventory": {
    "available": 32,
    "in_production": 18,
    "by_region": [
      {
        "region": {
          "id": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "region",
          "name": "Western Europe"
        },
        "available": 32,
        "in_production": 18
      }
    ]
  },
  "restock": [
    {
      "region": {
        "id": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "region",
        "name": "Western Europe"
      },
      "mode": "auto_buy",
      "when_available_below": 10,
      "reorder_units": 50
    }
  ],
  "images": [
    {
      "url": "https://cdn.example.com/clicker.png",
      "primary": true
    }
  ],
  "attachments": [
    {
      "id": "att_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "attachment",
      "name": "packing-guide.pdf"
    }
  ],
  "created_at": "2026-04-30T12:00:00Z",
  "updated_at": "2026-05-02T10:00:00Z"
}
```

## List stocks

GET `/v2/stocks`

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

- **For**: Designers

- **Operation id**: `list_stocks`

Lists your stocks, newest first, with how many units are available and in production. Filter on `in_stock=true` to see what can ship now. Designers only.

### Query parameters

-   `review_status` (array of ReviewStatus): Only stocks in these review states. Same shape as `ReviewStatus` above.
-   `in_stock` (string): Only stocks with (or without) units available to ship.
    
    Possible values: `true`, `false`.
    
-   `search` (string): Only stocks whose name contains this text, ignoring case.
-   `sort` (string): Order of results. A leading `-` means descending. `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`, `name`.
    
    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.

### Returns 200 OK

A page of stocks.

Response attributes (StockList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of StockSummary): The page of results.
    -   `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.
    -   `review` (Review): Same shape as `Review` above.
    -   `units_per_batch` (integer): How many units one purchased batch contains.
        
        Range at least 0.
        
    -   `batch_price` (object or null): What you pay for one batch, the same in every region. Set by 3DAPI when the stock is approved; null until then.
        -   `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.
    -   `unit_price` (object or null): What one unit costs you: `batch_price` divided by `units_per_batch`. Null until the stock 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.
    -   `images` (array of Image): Photos of the stock. 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.
    -   `inventory` (object): Units across every batch you bought.
        -   `available` (integer): Units made and ready to ship, across regions.
            
            Range at least 0.
            
        -   `in_production` (integer): Units paid for and not yet made, across regions.
            
            Range at least 0.
            
-   `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 stocks.
-   `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/stocks" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "stock",
      "name": "Clicker 50 pack",
      "review": {
        "status": "approved",
        "reason": null,
        "reviewed_at": "2026-05-02T10:00:00Z"
      },
      "units_per_batch": 50,
      "batch_price": {
        "amount": 30000,
        "currency": "usd"
      },
      "unit_price": {
        "amount": 600,
        "currency": "usd"
      },
      "inventory": {
        "available": 32,
        "in_production": 18
      },
      "images": [
        {
          "url": "https://cdn.example.com/clicker.png",
          "primary": true
        }
      ],
      "created_at": "2026-04-30T12:00:00Z",
      "updated_at": "2026-05-02T10:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Create a stock

POST `/v2/stocks`

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

- **For**: Designers

- **Operation id**: `create_stock`

Creates a stock (ready-made units you buy in batches) and submits it for review. It comes back pending, without a price or regions: 3DAPI sets the batch price and the production regions when approving it. Upload design files first with purpose `design_file`. Your plan must include prepaid stock. 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.
-   `description` (string or null): Longer description. Null for none.
-   `units_per_batch` (integer, required): How many units one batch contains.
    
    Range 1 to 100000.
    
-   `extras` (array of string): Your extras packed into every unit. Each counts once per unit. An extra still pending review is accepted and is reviewed with the stock; a rejected one is refused (`extra_not_approved`).
-   `manufacturing` (object): How the stock is produced. Applies to every design.
    -   `assembly_required` (boolean, required): Whether the printed parts must be assembled before shipping. `false` when `manufacturing` is omitted on create.
    -   `farm_instructions` (string or null): Notes for the production partner, for example packing steps. Omitted keeps the current notes; null or blank clears them. A change sends the stock back to review.
-   `images` (array of objects): Stock photos, in display order. On create, every entry is a new `upload`. On update the list replaces the current images: entries not sent are deleted, and kept ones use the `url` the stock returns.
    -   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.
-   `attachments` (array of objects): Production documents for the farm: new (`upload`, purpose `attachment`) or kept (`id`, update only). On update the list replaces the current attachments.
    -   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**:
        
        -   `id` (string, required): An attachment of this stock to keep.
-   `designs` (array of object, required): The model files of the stock. Add more later with `add_stock_design`, which sends the stock back to review.
    -   `file` (object, required): The model file, uploaded 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.

### Returns 201 Created

The new stock, pending review. Returns [the stock object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_object).

### Responses

-   `201`: The new stock, 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 does not include prepaid stock, which stocks are part of (`plan_limit_reached`); `details.fix_url` points at upgrading.
-   `422`: An extra id is not one of yours (`extra_not_found`) or names an extra 3DAPI rejected (`extra_not_approved`, its name in `details.extra_name`), a material id is not in the catalog (`material_not_found`), multicolor combinations have different slot counts (`slot_count_mismatch`), more than one image is primary (`too_many_primary_images`), an image or attachment is sent as one to keep although a new stock has none yet (`unknown_image`, `unknown_attachment`; send `{ "upload": "upl_..." }`), or an upload id is unknown, expired or for another purpose (`unknown_upload`, `upload_expired`, `upload_purpose_mismatch`, `upload_missing` when the file was never sent, `upload_already_used` when the upload id was already used 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/stocks" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Clicker 50 pack",
  "units_per_batch": 50,
  "designs": [
    {
      "file": {
        "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
      },
      "name": "Clicker",
      "materials": {
        "mode": "single",
        "allowed": [
          "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
        ]
      }
    }
  ],
  "extras": [
    "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
  ],
  "manufacturing": {
    "assembly_required": true,
    "farm_instructions": "Bag in tens."
  }
}'
```

## Get a stock

GET `/v2/stocks/{id}`

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

- **For**: Designers

- **Operation id**: `get_stock`

Returns one of your stocks: its price, contents, inventory per region and restock rules. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the stock, prefixed with `stk_`. 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[]=contents.design`. 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 `contents.extra` needs `extras:read`.
    
    Possible values: `contents.design`, `contents.extra`, `attachments`.
    

### Returns 200 OK

The stock. Returns [the stock object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_object).

### Responses

-   `200`: The stock.
-   `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 stock with this id (`stock_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/stocks/stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

## Update a stock

PATCH `/v2/stocks/{id}`

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

- **For**: Designers

- **Operation id**: `update_stock`

Changes the fields you send. Changing `units_per_batch` or the farm instructions sends the stock back to review; it cannot be bought until approved again. Designers only.

### Path parameters

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

### Request body

-   `name` (string): Display name.
-   `description` (string or null): Longer description. Null for none.
-   `units_per_batch` (integer): How many units one batch contains.
    
    Range 1 to 100000.
    
-   `extras` (array of string): Your extras packed into every unit. Each counts once per unit. An extra still pending review is accepted and is reviewed with the stock; a rejected one is refused (`extra_not_approved`).
-   `manufacturing` (object): How the stock is produced. Applies to every design.
    -   `assembly_required` (boolean, required): Whether the printed parts must be assembled before shipping. `false` when `manufacturing` is omitted on create.
    -   `farm_instructions` (string or null): Notes for the production partner, for example packing steps. Omitted keeps the current notes; null or blank clears them. A change sends the stock back to review.
-   `images` (array of objects): Stock photos, in display order. On create, every entry is a new `upload`. On update the list replaces the current images: entries not sent are deleted, and kept ones use the `url` the stock returns. Same shape as `ImageInput` above.
-   `attachments` (array of objects): Production documents for the farm: new (`upload`, purpose `attachment`) or kept (`id`, update only). On update the list replaces the current attachments.
    -   Shape **Reference** (default):
        
        Same shape as `UploadReference` above.
        
    -   Shape **object**:
        
        -   `id` (string, required): An attachment of this stock to keep.

### Returns 200 OK

The updated stock. Returns [the stock object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_object).

### Responses

-   `200`: The updated stock.
-   `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 stock with this id (`stock_not_found`).
-   `422`: An extra id is not one of yours (`extra_not_found`) or names an extra 3DAPI rejected (`extra_not_approved`, its name in `details.extra_name`), an image `url` or attachment `id` to keep is not one of the stock's (`unknown_image`, `unknown_attachment`), more than one image is primary (`too_many_primary_images`), or an upload id is unknown, expired or for another purpose.
-   `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/stocks/stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "units_per_batch": 50,
  "manufacturing": {
    "assembly_required": true,
    "farm_instructions": "Pack each unit in its own bag."
  }
}'
```

## Add a design to a stock

POST `/v2/stocks/{id}/designs`

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

- **For**: Designers

- **Operation id**: `add_stock_design`

Adds a model file to a stock. Sends the stock back to review. Edit or remove designs with the design operations. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the stock, prefixed with `stk_`. 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

-   `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): Same shape as `DesignMaterialsInput` above.

### Returns 201 Created

The new design, pending review. Returns [the design object](https://public-api.dapi.digital/docs/reference/designs.md#design_object).

### Responses

-   `201`: The new design, 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`: 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 stock with this id (`stock_not_found`).
-   `422`: A material id is not in the catalog (`material_not_found`), multicolor combinations have different slot counts (`slot_count_mismatch`), or the upload id is unknown, expired or for another purpose.
-   `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/stocks/stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/designs" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "file": {
    "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
  },
  "name": "Tracker lid",
  "materials": {
    "mode": "single",
    "allowed": [
      "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
    ]
  }
}'
```

## Set a restock rule

PUT `/v2/stocks/{id}/restock/{region_id}`

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

- **For**: Designers

- **Operation id**: `set_stock_restock`

Sets what happens when one region runs low (see `when_available_below` for exactly what is counted). Auto-reorder buys from the same wallet: when a region reaches its restock threshold and auto-reorder is on in your wallet settings, batches are bought automatically (`origin: auto_reorder`), so keep enough balance for both. Returns the stock with its updated `restock` list. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the stock, prefixed with `stk_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `region_id` (string, required): Unique identifier of the region, prefixed with `reg_`. Treat it as an opaque string: store it and compare it, never parse it.

### Request body

-   `mode` (string, required): `off` does nothing, `notify` emails you when the region runs low, `auto_buy` buys `reorder_units` from your wallet (when auto-reorder is on in your wallet settings; otherwise it emails).
-   `when_available_below` (integer or null): Threshold in units. The rule is checked each time an order draws units from this stock (when the order is assigned for production), not continuously. It counts the units paid for minus the units used in this region, across every batch and including units still in production (so not `available`), and triggers when that count is at or below this threshold. Required unless `mode` is `off`.
    
    Range 1 to 100000.
    
-   `reorder_units` (integer or null): Units to buy per automatic purchase, returned as set. Purchases round up to whole batches: an automatic purchase buys `reorder_units` divided by `units_per_batch`, rounded up (at least one batch), at `batch_price` each, and the batch it creates holds exactly those batches times `units_per_batch` units. A multiple of `units_per_batch` buys exactly what you ask for. Required for `auto_buy`.
    
    Range 1 to 100000.
    

### Returns 200 OK

The stock with the new rule. Returns [the stock object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_object).

### Responses

-   `200`: The stock with the new rule.
-   `400`: The request is invalid, including a field the mode needs that is missing or one that does not apply to it (`validation_failed`, `details.errors`).
-   `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`: `auto_buy` buys stock, so your plan must include prepaid stock (`plan_limit_reached`); `details.fix_url` points at upgrading.
-   `404`: You have no stock with this id (`stock_not_found`).
-   `422`: The region is not one of the stock regions (`region_not_available`).
-   `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 PUT "https://public-api.dapi.digital/v2/stocks/stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/restock/reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "auto_buy",
  "when_available_below": 10,
  "reorder_units": 50
}'
```

## The stock batch object

One purchase of a stock in one region: how many units were bought, how many are made and how many orders have used.

### Attributes

-   `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`.
-   `stock` (object): The stock this batch is of.
    -   `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.
-   `region` (object): Production region holding the units. Orders use units from a region that ships to the buyer.
    -   `id` (string): Unique identifier of the region, prefixed with `reg_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `region`.
    -   `name` (string): Display name of the region.
-   `status` (StockBatchStatus):
    
    Where the batch is:
    
    -   `queued`: paid for; production has not started.
    -   `in_production`: the production partner is making the units. Units already made can be used by orders.
    -   `completed`: every unit has been made.
    -   `used_up`: orders have used every unit.
    
-   `batches` (integer): How many batches were bought in this purchase line, at least one. Each holds the stock `units_per_batch`. An `auto_reorder` purchase rounds `reorder_units` up to whole batches.
    
    Range at least 1.
    
-   `units` (object): Unit counts for this batch.
    -   `purchased` (integer): Units bought in this purchase line: `batches` times units per batch.
        
        Range at least 0.
        
    -   `made` (integer): Units the production partner has made so far.
        
        Range at least 0.
        
    -   `used` (integer): Units orders have used.
        
        Range at least 0.
        
    -   `available` (integer): Units ready to ship: `made` minus `used`.
        
        Range at least 0.
        
-   `price_paid` (object): What you paid for this purchase line, all batches 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.
-   `unit_price` (object): What one unit cost you.
    -   `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.
-   `purchase_reference` (string or null): Reference of the checkout this batch was bought in, shared by every batch bought together and shown on the wallet charge.
-   `origin` (string): How the batch was bought: `manual` by you (dashboard or API), `auto_reorder` automatically from your wallet when a region reached its restock threshold.
-   `purchased_at` (timestamp): When the batch was bought.
-   `completed_at` (timestamp or null): When every unit was made. Null until then.

**The stock batch object**

```json
{
  "id": "bat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "stock_batch",
  "stock": {
    "id": "stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "stock",
    "name": "Clicker 50 pack"
  },
  "region": {
    "id": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "region",
    "name": "Western Europe"
  },
  "status": "in_production",
  "batches": 2,
  "units": {
    "purchased": 100,
    "made": 60,
    "used": 20,
    "available": 40
  },
  "price_paid": {
    "amount": 60000,
    "currency": "usd"
  },
  "unit_price": {
    "amount": 600,
    "currency": "usd"
  },
  "purchase_reference": "ST-8KD2X",
  "origin": "manual",
  "purchased_at": "2026-09-30T10:00:00Z",
  "completed_at": null
}
```

## List batches across stocks

GET `/v2/stock-batches`

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

- **For**: Designers

- **Operation id**: `list_all_stock_batches`

Lists every batch you bought, of any stock, newest first. Use it to see everything in production at once, or every batch bought in one checkout with `purchase_reference`. Designers only.

### Query parameters

-   `status` (array of StockBatchStatus): Only batches in these states. Repeat for several, for example `status=queued&status=in_production`. Same shape as `StockBatchStatus` above.
-   `region` (string): Only batches held in this region.
-   `sort` (string): Order of results. A leading `-` means newest first.
    
    Possible values: `-purchased_at`, `purchased_at`.
    
    Defaults to "-purchased\_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.
-   `stock` (string): Only batches of this stock.
-   `purchased_after` (timestamp): Only batches bought after this time.
-   `purchased_before` (timestamp): Only batches bought before this time.
-   `origin` (string): Only batches you bought (`manual`) or auto-reorder bought.
    
    Possible values: `manual`, `auto_reorder`.
    
-   `purchase_reference` (string): Only batches bought in this checkout, for example `ST-8KD2X`.

### Returns 200 OK

A page of batches.

Response attributes (StockBatchList):

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

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "bat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "stock_batch",
      "stock": {
        "id": "stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "stock",
        "name": "Clicker 50 pack"
      },
      "region": {
        "id": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "region",
        "name": "Western Europe"
      },
      "status": "in_production",
      "batches": 2,
      "units": {
        "purchased": 100,
        "made": 60,
        "used": 20,
        "available": 40
      },
      "price_paid": {
        "amount": 60000,
        "currency": "usd"
      },
      "unit_price": {
        "amount": 600,
        "currency": "usd"
      },
      "purchase_reference": "ST-8KD2X",
      "origin": "manual",
      "purchased_at": "2026-09-30T10:00:00Z",
      "completed_at": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## List what used a batch

GET `/v2/stocks/{id}/batches/{batch_id}/usage`

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

- **For**: Designers

- **Operation id**: `list_stock_batch_usage`

Lists the orders that took units from one batch, newest first. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the stock, prefixed with `stk_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `batch_id` (string, required): Unique identifier of the stock batch, prefixed with `bat_`. Treat it as an opaque string: store it and compare it, never parse it.

### Query parameters

-   `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 usage lines.

Response attributes (StockBatchUsageList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of StockBatchUsage): The page of results.
    -   `order` (object): The order that used the units.
        -   `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`.
        -   `name` (string): Display name of the order.
    -   `variant` (object): The variant 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.
    -   `units` (integer): Units this order line took from the batch.
        
        Range at least 1.
        
    -   `used_at` (timestamp): When the order was placed.
-   `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 usage lines.
-   `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 such stock, or the batch is not one of its batches (`stock_not_found`, `stock_batch_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/stocks/stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/batches/bat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/usage" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "order": {
        "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "order",
        "name": "SSG2M9S9"
      },
      "variant": {
        "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "variant",
        "name": "Clicker Blue"
      },
      "units": 2,
      "used_at": "2026-10-11T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## List the batches of a stock

GET `/v2/stocks/{id}/batches`

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

- **For**: Designers

- **Operation id**: `list_stock_batches`

Lists every batch you bought of one stock, newest first, with how many units are made, used and still available. Use it to follow production and see where your inventory is. Designers only.

### Path parameters

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

### Query parameters

-   `status` (array of StockBatchStatus): Only batches in these states. Repeat for several, for example `status=queued&status=in_production`. Same shape as `StockBatchStatus` above.
-   `region` (string): Only batches held in this region.
-   `sort` (string): Order of results. A leading `-` means newest first.
    
    Possible values: `-purchased_at`, `purchased_at`.
    
    Defaults to "-purchased\_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 batches.

Response attributes (StockBatchList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of StockBatch): The page of results.
    
    Described in [the stock batch object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_batch_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 batches.
-   `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 stock with this id (`stock_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/stocks/stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/batches" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "bat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "stock_batch",
      "stock": {
        "id": "stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "stock",
        "name": "Clicker 50 pack"
      },
      "region": {
        "id": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "region",
        "name": "Western Europe"
      },
      "status": "in_production",
      "batches": 2,
      "units": {
        "purchased": 100,
        "made": 60,
        "used": 20,
        "available": 40
      },
      "price_paid": {
        "amount": 60000,
        "currency": "usd"
      },
      "unit_price": {
        "amount": 600,
        "currency": "usd"
      },
      "purchase_reference": "ST-8KD2X",
      "origin": "manual",
      "purchased_at": "2026-09-30T10:00:00Z",
      "completed_at": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Get a stock batch

GET `/v2/stocks/{id}/batches/{batch_id}`

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

- **For**: Designers

- **Operation id**: `get_stock_batch`

Returns one batch of a stock with its production progress. Designers only.

### Path parameters

-   `id` (string, required): Unique identifier of the stock, prefixed with `stk_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `batch_id` (string, required): Unique identifier of the stock batch, prefixed with `bat_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The batch. Returns [the stock batch object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_batch_object).

### Responses

-   `200`: The batch.
-   `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 such stock, or the batch is not one of its batches (`stock_not_found`, `stock_batch_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/stocks/stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/batches/bat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

## The stock purchase object

One checkout of stock batches.

### Attributes

-   `object` (string): Names the type of this object. Always `stock_purchase`.
-   `purchase_reference` (string): Reference shared by every batch bought in this checkout.
-   `total` (object): What was taken from your wallet.
    -   `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.
-   `batches` (array of StockBatch): The batches bought, one per item.
    
    Described in [the stock batch object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_batch_object).
    

**The stock purchase object**

```json
{
  "object": "stock_purchase",
  "purchase_reference": "ST-8KD2X",
  "total": {
    "amount": 60000,
    "currency": "usd"
  },
  "batches": [
    {
      "id": "bat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "stock_batch",
      "stock": {
        "id": "stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "stock",
        "name": "Clicker 50 pack"
      },
      "region": {
        "id": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "region",
        "name": "Western Europe"
      },
      "status": "queued",
      "batches": 2,
      "units": {
        "purchased": 100,
        "made": 60,
        "used": 20,
        "available": 40
      },
      "price_paid": {
        "amount": 60000,
        "currency": "usd"
      },
      "unit_price": {
        "amount": 600,
        "currency": "usd"
      },
      "purchase_reference": "ST-8KD2X",
      "origin": "manual",
      "purchased_at": "2026-09-30T10:00:00Z",
      "completed_at": null
    }
  ]
}
```

## Buy stock batches

POST `/v2/stock-purchases`

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

- **For**: Designers

- **Operation id**: `create_stock_purchase`

Buys batches of one or more approved stocks in one checkout, paid from your wallet. There is no card top-up: if the balance is short, nothing is bought. Each item becomes a batch that the production partner of its region makes. Auto-reorder buys from the same wallet: when a region reaches its restock threshold and auto-reorder is on in your wallet settings, batches are bought automatically (`origin: auto_reorder`), so keep enough balance for both. Your plan must include prepaid stock. 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 buy. Each stock and region pair at most once.
    -   `stock` (string, required): An approved stock of yours.
    -   `region` (string, required): One of the stock regions, from the stock `restock` list.
    -   `batches` (integer, required): How many batches to buy for this region, each of `units_per_batch` units at the stock `batch_price`.
        
        Range 1 to 1000.
        

### Returns 201 Created

The purchase and the batches bought. Returns [the stock purchase object](https://public-api.dapi.digital/docs/reference/stocks.md#stock_purchase_object).

### Responses

-   `201`: The purchase and the batches bought.
-   `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`).
-   `402`: Your wallet balance does not cover the purchase (`insufficient_balance`); `details.required` is the amount needed and `details.balance` what the wallet holds. Nothing was bought.
-   `403`: Your plan does not include prepaid stock (`plan_limit_reached`); `details.fix_url` points at upgrading.
-   `409`: A stock is not approved and priced (`stock_not_approved`); `details.items` lists the positions concerned.
-   `422`: A stock is not one of yours (`stock_not_found`), a region is not one of the stock regions (`region_not_available`), or a stock and region pair appears twice (`duplicate_item`); `details.items` lists the positions concerned.
-   `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/stock-purchases" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "stock": "stk_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "region": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "batches": 2
    }
  ]
}'
```

## The region object

A production region your stock is held in, and the countries it ships to. Orders use stock from a region that ships to the buyer.

### Attributes

-   `id` (string): Unique identifier of the region, prefixed with `reg_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `region`.
-   `name` (string): Display name, as set by the production partner.
-   `countries` (array of string): Countries orders can ship to from this region.

**The region object**

```json
{
  "id": "reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "region",
  "name": "Western Europe",
  "countries": [
    "DE",
    "FR",
    "NL"
  ]
}
```

## Get a production region

GET `/v2/regions/{id}`

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

- **For**: Designers

- **Operation id**: `get_region`

Returns a region one of your stocks is produced in, with the countries it ships to. Only regions of your own stocks are available. Designers only.

### Path parameters

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

### Returns 200 OK

The region. Returns [the region object](https://public-api.dapi.digital/docs/reference/stocks.md#region_object).

### Responses

-   `200`: The region.
-   `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`: None of your stocks is produced in this region (`region_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/regions/reg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

---

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