# Quotes

Prices for printing model files. Integrators only.

## Endpoints

-   [GET `/v2/quotes`](https://public-api.dapi.digital/docs/reference/quotes.md#list_quotes)
-   [POST `/v2/quotes`](https://public-api.dapi.digital/docs/reference/quotes.md#create_quote)
-   [GET `/v2/quotes/{id}`](https://public-api.dapi.digital/docs/reference/quotes.md#get_quote)

## The quote object

A price for printing one model file. Integrators upload model files to get quotes; until the account is approved, 10 approved quotes are also what 3DAPI reviews to approve the account (see `get_integrator_onboarding`).

### Attributes

-   `id` (string): Unique identifier of the quote, prefixed with `qte_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `quote`.
-   `name` (string): Display name of the quote. The `name` you sent when creating it, otherwise the uploaded file name.
-   `file` (object): The model file you uploaded, exactly as you sent it, and the production version 3DAPI prepared from it.
    -   `name` (string): Name of the uploaded file.
    -   `size_bytes` (integer or null): Size of the uploaded file in bytes. Null for old quotes created before sizes were recorded.
    -   `download_url` (string): Signed link to download the uploaded file. It expires after 15 minutes; fetch the quote again for a fresh one.
    -   `production_version` (object or null):
        
        The copy 3DAPI adapted to the production printer (printer, process and filament settings written in), the same as a design file's `production_version`. This is the file that gets printed, and the one `price` is for.
        
        Null when your upload is printed as it is: while the quote is `processing` and the file is still being checked, when the check found nothing to change, and when the check could not be done (the quote is then priced on your upload, which is also what gets printed). It never changes once the quote is `ready`.
        
        -   `name` (string): File name.
        -   `format` (string): File format. Always `3mf`: a mesh upload (STL, OBJ, STEP) becomes a 3MF project.
        -   `size_bytes` (integer or null): File size in bytes. Null when it was not recorded.
        -   `download_url` (string): Signed link to download the file. It expires after 15 minutes; fetch the quote again for a fresh one.
        -   `prepared_at` (timestamp): When 3DAPI prepared this version.
-   `status` (QuoteStatus):
    
    Where the quote stands:
    
    -   `processing`: the file is being sliced to measure material and print time. Usually takes one to a few minutes.
    -   `ready`: slicing finished and `price` is set.
    -   `failed`: the file could not be sliced, for example a broken or empty model. `failure_reason` says why. Upload a corrected file as a new quote.
    
-   `price` (object or null): Price to print one unit of the model, in usd: the price of the file that gets printed, which is `file.production_version` when there is one, otherwise your upload. Null until `status` is `ready`, and on failed quotes.
    -   `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.
-   `failure_reason` (string or null): Why slicing failed, written to be shown to a person, usually with how to fix the file. For example, a binary STL the slicer cannot read (small models exported with zero normals are mistaken for text STL files) says to export it again with normals or as .3mf. Null unless `status` is `failed`. What a printable file needs: [https://public-api.dapi.digital/docs/guides/file-requirements](https://public-api.dapi.digital/docs/guides/file-requirements).
-   `suggested_materials` (array of SuggestedMaterial or null):
    
    One suggested catalog material per extruder slot the file prints with, in slot order, read from the colours and filament types in your file. Only slots something prints with are listed, so the numbers can have gaps (a project using filaments 2 and 4 lists slots 2 and 4).
    
    Order with `use_suggested_materials: true` on the order line to print with exactly these, or send `materials` as `[{ slot, material }]` for the same slots to choose your own.
    
    Null until `status` is `ready`, on failed quotes, and on quotes created before suggestions existed.
    
    -   `slot` (integer): Extruder slot (filament number in the slicer, starting at 1) that prints with this material. The print farm loads the material into this slot.
        
        Range at least 1.
        
    -   `file_color` (string or null): Colour your file gives this slot, as `#RRGGBB`. Null when the file has no colour for it (STL, OBJ and STEP files never do).
    -   `file_filament_type` (string or null): Filament type your file names for this slot, as written in the file (for example `PLA` or `PETG-HF`). Null when it names none. The suggestion stays within this plastic when the catalog has it, otherwise it is PLA.
    -   `material` (MaterialReference): Reference to a material. Catalog materials can be expanded to the full material; custom materials have no id and nothing more to expand.
        -   `id` (string or null): Unique identifier of a catalog material, prefixed with `mat_`. Null for a custom material.
        -   `object` (string): Names the type of this object. Always `material`.
        -   `name` (string): Display name, for example `PLA Black`. For custom materials, exactly what the seller typed.
        -   `custom` (boolean): True for a seller-specified material that is not in the catalog. Custom materials need manual review before production.
    -   `match` (SuggestedMaterialMatch):
        
        How close the suggested catalog material is to the colour in your file.
        
        -   `exact`: indistinguishable to the eye.
        -   `close`: the same colour, a shade apart.
        -   `approximate`: the nearest the catalog has, visibly different; also used when the file gives no colour. Check it before ordering.
        
-   `approval` (object): The file in your account approval. Approval only matters until your account is approved; afterwards new quotes stay `not_submitted` and are not reviewed one by one: an order goes to production once its quote is `ready`.
    -   `status` (QuoteApprovalStatus):
        
        Where the file stands in your account approval (onboarding):
        
        -   `not_submitted`: not part of any approval request yet.
        -   `in_review`: part of the approval request 3DAPI is reviewing.
        -   `approved`: approved by 3DAPI; it counts toward the 10 approved files your account needs and stays approved.
        -   `rejected`: refused by 3DAPI; `reason` says why. It cannot be submitted again: upload a corrected file as a new quote and submit that instead.
        
    -   `reason` (string or null): Why 3DAPI rejected the file. Null unless `status` is `rejected`. What a file needs to be approved: [https://public-api.dapi.digital/docs/guides/file-requirements](https://public-api.dapi.digital/docs/guides/file-requirements).
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `finished_at` (timestamp or null): When slicing finished, successfully or not. Null while `processing`.

**The quote object**

```json
{
  "id": "qte_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "quote",
  "name": "Phone stand v2",
  "file": {
    "name": "phone-stand-v2.3mf",
    "size_bytes": 2758723,
    "download_url": "https://files.example.com/public-api/quotes/7f3a/input/phone-stand-v2.3mf?signature=abc",
    "production_version": {
      "name": "phone-stand-v2.3mf",
      "format": "3mf",
      "size_bytes": 2913004,
      "download_url": "https://files.example.com/public-api/quotes/7f3a/production/phone-stand-v2.3mf?signature=def",
      "prepared_at": "2026-10-20T09:12:09Z"
    }
  },
  "status": "ready",
  "suggested_materials": [
    {
      "slot": 1,
      "file_color": "#C12E1F",
      "file_filament_type": "PLA",
      "material": {
        "id": "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "material",
        "name": "PLA Red",
        "custom": false
      },
      "match": "exact"
    },
    {
      "slot": 3,
      "file_color": "#0B2A88",
      "file_filament_type": null,
      "material": {
        "id": "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "material",
        "name": "PLA Blue",
        "custom": false
      },
      "match": "close"
    }
  ],
  "price": {
    "amount": 1245,
    "currency": "usd"
  },
  "failure_reason": null,
  "approval": {
    "status": "not_submitted",
    "reason": null
  },
  "created_at": "2026-10-20T09:12:00Z",
  "finished_at": "2026-10-20T09:13:41Z"
}
```

## List quotes

GET `/v2/quotes`

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

- **For**: Integrators

- **Operation id**: `list_quotes`

Lists your quotes, newest first, with their price and approval status. Use the filters to find quotes to submit for approval (`status=ready&approval_status=not_submitted`). Integrators only; designers are refused with `actor_not_allowed`.

### Query parameters

-   `status` (array of QuoteStatus): Only quotes in these states. Repeat for several. Same shape as `QuoteStatus` above.
-   `approval_status` (array of QuoteApprovalStatus): Only quotes in these approval states, for example `approval_status=not_submitted&status=ready` for the quotes you can still submit for approval. Same shape as `QuoteApprovalStatus` above.
-   `created_after` (timestamp): Only quotes created at or after this time (ISO 8601).
-   `created_before` (timestamp): Only quotes created before this time (ISO 8601).
-   `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 quotes.

Response attributes (QuoteList):

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

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "qte_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "quote",
      "name": "Phone stand v2",
      "file": {
        "name": "phone-stand-v2.3mf",
        "size_bytes": 2758723,
        "download_url": "https://files.example.com/public-api/quotes/7f3a/input/phone-stand-v2.3mf?signature=abc",
        "production_version": {
          "name": "phone-stand-v2.3mf",
          "format": "3mf",
          "size_bytes": 2913004,
          "download_url": "https://files.example.com/public-api/quotes/7f3a/production/phone-stand-v2.3mf?signature=def",
          "prepared_at": "2026-10-20T09:12:09Z"
        }
      },
      "status": "ready",
      "suggested_materials": [
        {
          "slot": 1,
          "file_color": "#C12E1F",
          "file_filament_type": "PLA",
          "material": {
            "id": "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
            "object": "material",
            "name": "PLA Red",
            "custom": false
          },
          "match": "exact"
        },
        {
          "slot": 3,
          "file_color": "#0B2A88",
          "file_filament_type": null,
          "material": {
            "id": "mat_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
            "object": "material",
            "name": "PLA Blue",
            "custom": false
          },
          "match": "close"
        }
      ],
      "price": {
        "amount": 1245,
        "currency": "usd"
      },
      "failure_reason": null,
      "approval": {
        "status": "not_submitted",
        "reason": null
      },
      "created_at": "2026-10-20T09:12:00Z",
      "finished_at": "2026-10-20T09:13:41Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Quote a model file

POST `/v2/quotes`

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

- **For**: Integrators

- **Operation id**: `create_quote`

Starts pricing one model file and returns the quote with `status: processing`. Poll `get_quote` until it is `ready` (with `price`) or `failed`.

While it is processing, 3DAPI first checks the file against its production printers and, when needed, prepares a production version with the right printer, process and filament settings (`file.production_version`). The price is for that version, since it is the file that gets printed; your upload stays downloadable as it is.

Upload the file first: `POST /v2/uploads` with purpose `quote_file`, PUT the bytes, then send the upload id here.

Limits before your account is approved: up to 25 quotes per round, and none while an approval request is in review. After a review that rejected files you get a fresh round of 25. Once approved, quotes are unlimited. A quote counts from the moment it is created, while it is processing and once it is ready; a quote that ends `failed` is given back to your allowance. `get_integrator_onboarding` shows how many you have used.

Integrators only; designers are refused with `actor_not_allowed`.

### 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 to quote, as `{ "upload": "upl_..." }` from `POST /v2/uploads` with purpose `quote_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): Display name for the quote. Defaults to the uploaded file name.

### Returns 201 Created

The quote, being processed. Returns [the quote object](https://public-api.dapi.digital/docs/reference/quotes.md#quote_object).

### Responses

-   `201`: The quote, being processed.
-   `400`: The request is invalid (`validation_failed`).
-   `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.
-   `409`: Your account cannot quote right now: you used the 25 uploads of this round, so request approval next (`upload_limit_reached`), or an approval request is in review and uploads resume once it is reviewed (`approval_in_review`).
-   `422`: The upload is unknown, expired or of the wrong purpose (`unknown_upload`, `upload_expired`, `upload_purpose_mismatch`), its file was never sent to `upload_url` (`upload_missing`), or the upload id was already used by another quote (`upload_already_used`; every upload id works once).
-   `429`: The daily quote limit for accounts not yet approved is reached (`daily_quote_limit_reached`). It resets at midnight UTC. 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/quotes" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "file": {
    "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
  },
  "name": "Phone stand v2"
}'
```

## Get a quote

GET `/v2/quotes/{id}`

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

- **For**: Integrators

- **Operation id**: `get_quote`

Returns one of your quotes. Poll it after `create_quote` until `status` is `ready` or `failed`; every few seconds is enough. Integrators only; designers are refused with `actor_not_allowed`.

### Path parameters

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

### Returns 200 OK

The quote. Returns [the quote object](https://public-api.dapi.digital/docs/reference/quotes.md#quote_object).

### Responses

-   `200`: The quote.
-   `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 quote with this id (`quote_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/quotes/qte_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

---

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