# Integrator onboarding

Signing up as an integrator and getting the account approved.

## Endpoints

-   [POST `/v2/integrator/accounts`](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#create_integrator_account)
-   [POST `/v2/integrator/accounts/confirmation`](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#resend_integrator_confirmation)
-   [GET `/v2/integrator/onboarding`](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#get_integrator_onboarding)
-   [GET `/v2/integrator/approval-requests`](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#list_approval_requests)
-   [POST `/v2/integrator/approval-requests`](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#create_approval_request)
-   [GET `/v2/integrator/approval-requests/{id}`](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#get_approval_request)

## The approval request object

A set of your quotes sent to 3DAPI to approve your integrator account. 3DAPI approves or rejects each file separately; the account is approved once 10 files are approved.

### Attributes

-   `id` (string): Unique identifier of the approval request, prefixed with `apr_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `approval_request`.
-   `status` (ApprovalRequestStatus):
    
    -   `in_review`: 3DAPI is reviewing the files. You cannot upload or send another request until it is reviewed.
    -   `reviewed`: every file has a decision. If all were approved and you reached the required number, your account is approved; otherwise replace the rejected files and send a new request.
    
-   `quotes` (array of object): The quotes submitted in this request, with the decision on each file.
    -   `quote` (QuoteReference): Reference to a quote. Pass the matching `expand[]` path to receive the full quote instead.
        -   `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.
    -   `decision` (QuoteDecision):
        
        -   `pending`: not decided yet.
        -   `approved`: the file is approved and counts toward your account approval.
        -   `rejected`: the file is refused; `reason` says why. Upload a corrected file and include the new quote in your next request.
        
    -   `reason` (string or null): Why the file was rejected, written by the reviewer. Null unless `decision` is `rejected`.
-   `submitted_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `reviewed_at` (timestamp or null): When the review was completed. Null while `in_review`.

**The approval request object**

```json
{
  "id": "apr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "approval_request",
  "status": "reviewed",
  "quotes": [
    {
      "quote": {
        "id": "qte_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "quote",
        "name": "Phone stand v2"
      },
      "decision": "rejected",
      "reason": "The model has open edges; close the mesh and upload it again."
    }
  ],
  "submitted_at": "2026-10-20T10:00:00Z",
  "reviewed_at": "2026-10-21T08:30:00Z"
}
```

## Sign up as an integrator

POST `/v2/integrator/accounts`

Public: no API key needed.

- **Credentials**: None: the endpoint is public.

- **For**: Integrators

- **Operation id**: `create_integrator_account`

Creates an integrator account: a company building on the 3DAPI API. No credentials are needed and none are returned.

We email a confirmation link to the address. Open it to confirm the email (it leads to the API dashboard); only then can you sign in with the email and password and use the session or an API key for every other call. The link expires; ask for a new one with `resend_integrator_confirmation`.

An integrator account can never also be a seller account on 3DAPI, and a seller account cannot be converted: use an email address that has no seller account.

Limited to 10 sign-ups per IP address per hour (`rate_limited`, 429).

### Request body

-   `email` (string, required): Email address to sign in with. 3DAPI sends account and approval emails here.
-   `password` (string, required): Password to sign in with, 8 to 72 characters.
-   `company_name` (string, required): Name of the company building the integration.

### Returns 201 Created

The account was created and the confirmation email sent. `email_confirmed` is `false`.

Response attributes (IntegratorAccount):

-   `id` (string): Unique identifier of the integrator account, prefixed with `itg_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `integrator_account`.
-   `email` (string): The email address to sign in with.
-   `company_name` (string): Name of the company.
-   `email_confirmed` (boolean): Whether the email address is confirmed. A new account starts `false`: open the link in the confirmation email before signing in. Signing in is refused until then.
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.

### Responses

-   `201`: The account was created and the confirmation email sent. `email_confirmed` is `false`.
-   `400`: The request is invalid (`validation_failed`), or the password is too weak for the sign-in service (`weak_password`).
-   `409`: An account with this email already exists (`email_taken`). Sign in instead, or use another email.
-   `429`: Too many requests (`rate_limited`): the limit is 10 per hour per IP address. Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.
-   `503`: Sign-up is not available right now, or could not be finished (`signup_unavailable`). Nothing is kept in either case, so the same email can be used again. Try again later or contact [support@dapi.digital](mailto:support@dapi.digital).

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/integrator/accounts" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "dev@acme.example",
  "password": "correct-horse-battery",
  "company_name": "Acme Prints"
}'
```

**Response 201**

```json
{
  "id": "itg_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "integrator_account",
  "email": "dev@acme.example",
  "company_name": "Acme Prints",
  "email_confirmed": false,
  "created_at": "2026-10-20T09:00:00Z"
}
```

## Resend the confirmation email

POST `/v2/integrator/accounts/confirmation`

Public: no API key needed.

- **Credentials**: None: the endpoint is public.

- **For**: Integrators

- **Operation id**: `resend_integrator_confirmation`

Sends a new confirmation link to an integrator account whose email is not confirmed yet, for example when the first link expired or never arrived. No credentials are needed.

The answer is always 202, whether or not such an account exists or is already confirmed, so this operation never reveals which emails have accounts. Nothing is sent when there is no unconfirmed integrator account for the address.

Limited to 5 requests per IP address per hour (`rate_limited`, 429).

### Request body

-   `email` (string, required): The email address the account was created with.

### Returns 202 Accepted

Accepted. If an unconfirmed integrator account uses this email, a new confirmation link is on its way. No body.

### Responses

-   `202`: Accepted. If an unconfirmed integrator account uses this email, a new confirmation link is on its way. No body.
-   `400`: The request is invalid (`validation_failed`).
-   `429`: Too many requests (`rate_limited`): the limit is 5 per hour per IP address. Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.
-   `503`: Sending confirmations is not available right now (`signup_unavailable`). Try again later or contact [support@dapi.digital](mailto:support@dapi.digital).

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/integrator/accounts/confirmation" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "dev@acme.example"
}'
```

**Response 202**

```json
null
```

## Get your onboarding status

GET `/v2/integrator/onboarding`

- **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_integrator_onboarding`

Returns where your integrator account stands: approved files so far, your upload allowance, how many quotes the next approval request needs and the latest request with its per-file decisions. Use it to decide the next step: upload more files, send a request, wait for the review or replace rejected files. Integrators only; designers are refused with `actor_not_allowed`.

### Returns 200 OK

Your onboarding status.

Response attributes (IntegratorOnboarding):

-   `object` (string): Names the type of this object. Always `integrator_onboarding`.
-   `status` (OnboardingStatus):
    
    -   `collecting`: upload files to get quotes, then send an approval request with `files_to_submit` ready quotes.
    -   `in_review`: an approval request is being reviewed. Uploads are paused until it is reviewed.
    -   `changes_requested`: the last review rejected some files. Replace them with new quotes and send another request with `files_to_submit` quotes; you have a fresh upload allowance.
    -   `approved`: your account is approved. Quotes are unlimited and you can place orders.
    
-   `approved_files` (integer): How many of your files 3DAPI has approved so far.
    
    Range at least 0.
    
-   `required_files` (integer): How many approved files your account needs to be approved. Always 10 today.
-   `uploads` (object): Your upload allowance. Before approval each round allows 25 quotes; a review that rejects files starts a new round.
    -   `used` (integer): Quotes created in the current round that count toward the allowance: processing and ready ones. A quote that ends `failed` does not count.
        
        Range at least 0.
        
    -   `limit` (integer or null): Quotes allowed in the current round (25). Null once your account is approved: quotes are then unlimited.
-   `files_to_submit` (integer): How many ready quotes your next approval request must contain: the required number at first, then the number of files the last review rejected. 0 while a request is in review and once approved.
    
    Range at least 0.
    
-   `current_request` (object or null): Your latest approval request, in review or reviewed. Null before your first request.
    -   `id` (string): Unique identifier of the approval request, prefixed with `apr_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `approval_request`.
    -   `status` (ApprovalRequestStatus): Same shape as `ApprovalRequestStatus` above.
    -   `quotes` (array of object): The quotes submitted in this request, with the decision on each file.
        -   `quote` (QuoteReference): Same shape as `QuoteReference` above.
        -   `decision` (QuoteDecision): Same shape as `QuoteDecision` above.
        -   `reason` (string or null): Why the file was rejected, written by the reviewer. Null unless `decision` is `rejected`.
    -   `submitted_at` (timestamp): Point in time, ISO 8601 in UTC.
    -   `reviewed_at` (timestamp or null): When the review was completed. Null while `in_review`.
-   `order_blockers` (array of string): Account setup still missing before you can place orders, apart from approval. `billing_details_missing`: add your business billing details in the dashboard (Finance > Wallet); until then orders are refused with `compliance_blocked`. Empty when nothing is missing. Selling to EU or UK customers from outside the EU and UK also needs an EU responsible person, checked per order.

### Responses

-   `200`: Your onboarding status.
-   `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/integrator/onboarding" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "integrator_onboarding",
  "status": "changes_requested",
  "approved_files": 9,
  "required_files": 10,
  "uploads": {
    "used": 0,
    "limit": 25
  },
  "files_to_submit": 1,
  "current_request": {
    "id": "apr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "approval_request",
    "status": "reviewed",
    "quotes": [
      {
        "quote": {
          "id": "qte_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "quote",
          "name": "Phone stand v2"
        },
        "decision": "rejected",
        "reason": "The model has open edges; close the mesh and upload it again."
      }
    ],
    "submitted_at": "2026-10-20T10:00:00Z",
    "reviewed_at": "2026-10-21T08:30:00Z"
  },
  "order_blockers": [
    "billing_details_missing"
  ]
}
```

## List approval requests

GET `/v2/integrator/approval-requests`

- **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_approval_requests`

Lists your approval requests, newest first, with the decision on each file. Integrators only; designers are refused with `actor_not_allowed`.

### Query parameters

-   `status` (array of ApprovalRequestStatus): Only requests in these states. Same shape as `ApprovalRequestStatus` above.
-   `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 approval requests.

Response attributes (ApprovalRequestList):

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

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "apr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "approval_request",
      "status": "reviewed",
      "quotes": [
        {
          "quote": {
            "id": "qte_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
            "object": "quote",
            "name": "Phone stand v2"
          },
          "decision": "rejected",
          "reason": "The model has open edges; close the mesh and upload it again."
        }
      ],
      "submitted_at": "2026-10-20T10:00:00Z",
      "reviewed_at": "2026-10-21T08:30:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Submit sample files for review

POST `/v2/integrator/approval-requests`

- **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_approval_request`

Sends ready quotes to 3DAPI as sample files for review: 3DAPI checks that the files print well, which is about your files, not about access for an app or agent. Your account is approved once 10 files are approved. What a file needs: [https://public-api.dapi.digital/docs/guides/file-requirements](https://public-api.dapi.digital/docs/guides/file-requirements).

Send `quotes` to choose the files, exactly `files_to_submit` of them (see `get_integrator_onboarding`), each `ready` and never submitted. Omit `quotes` to submit your latest eligible quotes automatically.

The first request needs 10 quotes. Approved files stay approved, so after a review with rejections the next request needs only as many new quotes as were rejected. Rejected quotes cannot be submitted again: upload corrected files first.

While the request is in review you cannot create quotes or send another request. The review usually takes 24 to 48 hours; you get an email when the review is done.

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

-   `quotes` (array of string): The quotes to submit, exactly `files_to_submit` of them, each `ready` and `not_submitted`. Omit it to submit your latest eligible quotes automatically.

### Returns 201 Created

The request, in review. Returns [the approval request object](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#approval_request_object).

### Responses

-   `201`: The request, in review.
-   `400`: The request is invalid (`validation_failed`), for example `quotes` does not hold exactly `files_to_submit` ids.
-   `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 is already approved (`already_approved`), or a request is already in review (`approval_in_review`).
-   `422`: Without `quotes`, you have fewer eligible quotes than `files_to_submit` (`not_enough_ready_quotes`, `details.required` and `details.available`). With `quotes`, one of them is unknown, listed twice, still being priced, failed, already submitted, approved or rejected (`quote_not_eligible`, `details.quote` and `details.reason`: `not_found`, `duplicate`, `not_ready`, `failed`, `in_review`, `approved` or `rejected`).
-   `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/integrator/approval-requests" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## Get an approval request

GET `/v2/integrator/approval-requests/{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_approval_request`

Returns one of your approval requests with the decision and reason for each file. Integrators only; designers are refused with `actor_not_allowed`.

### Path parameters

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

### Returns 200 OK

The approval request. Returns [the approval request object](https://public-api.dapi.digital/docs/reference/integrator-onboarding.md#approval_request_object).

### Responses

-   `200`: The approval request.
-   `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 approval request with this id (`approval_request_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/integrator/approval-requests/apr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

---

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