Quality control

View as Markdown

Quality checks, partner questions and issue reports on orders.

The QC ticket object

A quality check, a question from the production partner, or an issue you reported on an order.

Attributes

  • idstring

    Unique identifier of the qc ticket, prefixed with qct_. Treat it as an opaque string: store it and compare it, never parse it.

  • objectstring

    Names the type of this object. Always qc_ticket.

  • kindQcTicketKind

    What the ticket is, which also says who opened it:

    • quality_check: 3DAPI asked the production partner for photos of what it made, before shipping. You accept or refuse each product. Opened by 3DAPI, automatically or by our team.
    • question: the production partner needs an answer from you before it prints. Opened by the production partner.
    • issue_report: you reported a defect after delivery and asked for a replacement. Opened by you; 3DAPI decides the replacement, and that decision closes the report.
    Possible valuesquality_checkquestionissue_report
  • reasonstring or null

    Why a quality check was raised. Null for questions and issue reports.

    Possible valuesonboardingspot_checkcapabilitysuspension
  • statusQcTicketStatus

    Where the ticket is. Quality checks:

    • awaiting_photos: the production partner owes photos.
    • awaiting_your_review: photos are in; accept or refuse each product.
    • refusal_under_review: you refused a product and 3DAPI is ruling on it.
    • reprinting: a refusal was upheld and the partner is remaking the product.
    • approved: every product was accepted; the order can ship.

    Questions and issue reports:

    • awaiting_answer: waiting for a reply: from you on a question, from 3DAPI on an issue report.
    • answered: you answered the question; the production partner has the next word.
    • closed: settled. A question is closed by the production partner who asked it; an issue report closes when 3DAPI decides the replacement.

    Any kind:

    • cancelled: the ticket no longer applies, for example after the order moved to another partner.
    Possible valuesawaiting_photosawaiting_your_reviewrefusal_under_reviewreprintingapprovedawaiting_answeransweredclosedcancelled
  • needs_your_actionboolean

    True when the next step is yours: photos are waiting for your review, or a question is waiting for your answer.

  • blocks_shippingboolean

    True when the order cannot ship until this ticket is approved. Questions and issue reports never block.

  • orderobject

    The order this ticket is about.

  • roundinteger

    Current round of photos.

    Range at least 1.
  • replacementQcReplacement or null

    Only on issue reports: the replacement you asked for and what was decided.

  • created_attimestamp

    Point in time, ISO 8601 in UTC.

  • submitted_attimestamp or null

    When the production partner last submitted photos.

  • resolved_attimestamp or null

    When the ticket was approved, closed or cancelled.

  • itemsarray of QcItem

    The products on the ticket. A conversation has one container item for its messages.

The QC ticket object
{
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  
}

Review a quality check

POST/v2/qc-tickets/{id}/review

Credentials
An API key with the qc:write scope (full access includes every scope). A signed-in dashboard session works too.
For
Designers
Operation id
review_qc_ticket

Accepts or refuses the products on a quality check, all in one request: either every verdict is recorded or none is. Rule on any product whose status is in_review (the ticket shows needs_your_action: true); this works even while another product on the ticket is already refusal_under_review. Accepting every product approves the ticket and lets the order ship. A refusal goes to 3DAPI, which rules on it (refusal_under_review); if upheld, the partner reprints the product and sends new photos. Same effect as reviewing in the dashboard. Designers only.

Path parameters

  • idstringRequired

    Unique identifier of the qc ticket, prefixed with qct_. Treat it as an opaque string: store it and compare it, never parse it.

Headers

  • Idempotency-Keystring

    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

  • itemsarray of objectsRequired

    One verdict per product you are ruling on.

Returns200 OK

The ticket after your review. Returns the QC ticket object.

Responses

  • 200

    The ticket after your 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 QC ticket with this id (qc_ticket_not_found), or a product is not on it (qc_item_not_found, details.field names the entry).

  • 409

    The ticket is not a quality check (not_a_quality_check), or it is closed or a product is not in review (not_awaiting_review).

  • 422

    A product is listed twice (duplicate_item), or a refusal points at a photo that is not one of that product's current photos (photo_not_on_item).

  • 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.

POST/v2/qc-tickets/{id}/review
curl -X POST "https://public-api.dapi.digital/v2/qc-tickets/qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/review" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "item": "qci_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "verdict": "accept"
    },
    {
      "item": "qci_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "verdict": "refuse",
      "reason": "Layer lines on the front face. Please reprint slower.",
      "photos": [
        "qcp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
      ]
    }
  ]
}'
Response
{
  "id": "qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "qc_ticket",
  "kind": "quality_check",
  "reason": "spot_check",
  "status": "awaiting_your_review",
  "needs_your_action": true,
  "blocks_shipping": true,
  "order": {
    "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "order",
    "name": "PV-SCN-1701A372"
  },
  "round": 1,
  "replacement": null,
  "created_at": "2026-10-09T15:20:00Z",
  "submitted_at": "2026-10-10T09:01:00Z",
  "resolved_at": null,
  "items": [
    {
      "id": "qci_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "qc_item",
      "title": "Clicker Blue",
      "product": {
        "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "product",
        "name": "Clicker"
      },
      "variant": {
        "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "variant",
        "name": "Clicker Blue"
      },
      "status": "in_review",
      "round": 1,
      "photos": [
        {
          "id": "qcp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "url": "https://files.example.com/qc/clicker-front.jpg?signature=abc",
          "round": 1,
          "uploaded_at": "2026-10-10T09:00:00Z"
        }
      ],
      "history": [
        {
          "action": "submitted",
          "by": "production_partner",
          "round": 1,
          "note": null,
          "at": "2026-10-10T09:01:00Z"
        }
      ]
    }
  ]
}

Reply on a QC conversation

POST/v2/qc-tickets/{id}/messages

Credentials
An API key with the qc:write scope (full access includes every scope). A signed-in dashboard session works too.
For
Designers
Operation id
send_qc_message

Answers a question from the production partner (kind: question). Turns alternate: you can post only when the partner spoke last (the ticket is awaiting_answer). Photos are uploaded first with purpose qc_photo. Issue reports take no messages: 3DAPI answers one by deciding the replacement. Designers only.

Path parameters

  • idstringRequired

    Unique identifier of the qc ticket, prefixed with qct_. Treat it as an opaque string: store it and compare it, never parse it.

Headers

  • Idempotency-Keystring

    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

  • bodystringRequired

    Your answer to the production partner, up to 5000 characters. Kept in the item history as answered.

  • photosarray of UploadReference

    Photos to attach, uploaded first with purpose qc_photo.

Returns200 OK

The ticket with your message. Returns the QC ticket object.

Responses

  • 200

    The ticket with your message.

  • 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 QC ticket with this id (qc_ticket_not_found).

  • 409

    It is not your turn (not_your_turn), the ticket is not a question (not_a_conversation), or the question is closed (ticket_closed).

  • 422

    An upload is unknown, expired or of the wrong 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.

POST/v2/qc-tickets/{id}/messages
curl -X POST "https://public-api.dapi.digital/v2/qc-tickets/qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/messages" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "body": "Yes, print it in matte black. The photo shows where the logo goes.",
  "photos": [
    {
      "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
    }
  ]
}'
Response
{
  "id": "qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "qc_ticket",
  "kind": "quality_check",
  "reason": "spot_check",
  "status": "awaiting_your_review",
  "needs_your_action": true,
  "blocks_shipping": true,
  "order": {
    "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "order",
    "name": "PV-SCN-1701A372"
  },
  "round": 1,
  "replacement": null,
  "created_at": "2026-10-09T15:20:00Z",
  "submitted_at": "2026-10-10T09:01:00Z",
  "resolved_at": null,
  "items": [
    {
      "id": "qci_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "qc_item",
      "title": "Clicker Blue",
      "product": {
        "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "product",
        "name": "Clicker"
      },
      "variant": {
        "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "variant",
        "name": "Clicker Blue"
      },
      "status": "in_review",
      "round": 1,
      "photos": [
        {
          "id": "qcp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "url": "https://files.example.com/qc/clicker-front.jpg?signature=abc",
          "round": 1,
          "uploaded_at": "2026-10-10T09:00:00Z"
        }
      ],
      "history": [
        {
          "action": "submitted",
          "by": "production_partner",
          "round": 1,
          "note": null,
          "at": "2026-10-10T09:01:00Z"
        }
      ]
    }
  ]
}

List QC tickets

GET/v2/qc-tickets

Credentials
An API key with the qc:read scope (full access includes every scope). A signed-in dashboard session works too.
For
Designers
Operation id
list_qc_tickets

Lists quality checks, questions from production partners and issues you reported, newest first. Filter on needs_your_action=true to find what is waiting on you, or on blocks_shipping=true to see what holds orders back. Designers only.

Query parameters

  • kindarray of QcTicketKind

    Only these kinds.

    Possible valuesquality_checkquestionissue_report
  • statusarray of QcTicketStatus

    Only tickets in these states.

    Possible valuesawaiting_photosawaiting_your_reviewrefusal_under_reviewreprintingapprovedawaiting_answeransweredclosedcancelled
  • needs_your_actionstring

    Only tickets that are (or are not) waiting on you.

    Possible valuestruefalse
  • blocks_shippingstring

    Only tickets that are (or are not) holding an order back.

    Possible valuestruefalse
  • orderstring

    Only tickets on this order.

  • created_aftertimestamp

    Only tickets opened after this time.

  • sortstring

    Order of results. A leading - means newest first.

    Possible values-created_atcreated_at
    Defaults to "-created_at".
  • limitinteger

    How many results per page, from 1 to 100.

    Range 1 to 100. Defaults to 20.
  • cursorstring

    Where to continue from: the next_cursor of the previous page. Omit for the first page.

Returns200 OK

A page of tickets.

Responses

  • 200

    A page of tickets.

  • 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.

GET/v2/qc-tickets
curl "https://public-api.dapi.digital/v2/qc-tickets" \
  -H "x-api-key: $DAPI_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "id": "qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "qc_ticket",
      "kind": "quality_check",
      "reason": "spot_check",
      "status": "awaiting_your_review",
      "needs_your_action": true,
      "blocks_shipping": true,
      "order": {
        "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "order",
        "name": "PV-SCN-1701A372"
      },
      "round": 1,
      "replacement": null,
      "created_at": "2026-10-09T15:20:00Z",
      "submitted_at": "2026-10-10T09:01:00Z",
      "resolved_at": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Report an issue with a delivered order

POST/v2/qc-tickets

Credentials
An API key with the qc:write scope (full access includes every scope). A signed-in dashboard session works too.
For
Designers
Operation id
create_qc_ticket

Opens an issue report on a completed order: what came back wrong, how many, and photos (uploaded first with purpose qc_photo). 3DAPI reviews it and decides on a replacement and who pays for it (replacement); that decision closes the report. The production partner is not part of the report. It never holds an order. Designers only.

Headers

  • Idempotency-Keystring

    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

  • orderstringRequired

    The delivered order the issue is about. It must be completed.

  • itemsarray of objectRequired

    What came back wrong and needs replacing.

  • messagestringRequired

    What happened, for 3DAPI to rule on.

  • photosarray of UploadReference

    Photos to attach, uploaded first with purpose qc_photo.

Returns201 Created

The new issue report. Returns the QC ticket object.

Responses

  • 201

    The new issue report.

  • 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 order (order_not_found), or an item is not on it (order_item_not_found, details.field names the entry).

  • 409

    Issues can only be reported on completed orders (order_not_eligible).

  • 422

    An item is listed twice (duplicate_item), asks for more units than were ordered (quantity_exceeds_ordered, details.ordered), or an upload is unknown, expired or of the wrong 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.

POST/v2/qc-tickets
curl -X POST "https://public-api.dapi.digital/v2/qc-tickets" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "order": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "items": [
    {
      "order_item": "itm_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "quantity": 1
    }
  ],
  "message": "The lid arrived cracked along the hinge.",
  "photos": [
    {
      "upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
    }
  ]
}'
Response
{
  "id": "qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "qc_ticket",
  "kind": "issue_report",
  "reason": null,
  "status": "awaiting_answer",
  "needs_your_action": false,
  "blocks_shipping": false,
  "order": {
    "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "order",
    "name": "PV-SCN-1701A372"
  },
  "round": 1,
  "replacement": {
    "items": [
      {
        "variant": {
          "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "variant",
          "name": "Clicker Blue"
        },
        "quantity": 1
      }
    ],
    "decision": "pending",
    "paid_by": null,
    "order": null
  },
  "created_at": "2026-10-09T15:20:00Z",
  "submitted_at": "2026-10-10T09:01:00Z",
  "resolved_at": null,
  "items": [
    {
      "id": "qci_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "qc_item",
      "title": "Clicker Blue",
      "product": {
        "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "product",
        "name": "Clicker"
      },
      "variant": {
        "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "variant",
        "name": "Clicker Blue"
      },
      "status": "in_review",
      "round": 1,
      "photos": [
        {
          "id": "qcp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "url": "https://files.example.com/qc/clicker-front.jpg?signature=abc",
          "round": 1,
          "uploaded_at": "2026-10-10T09:00:00Z"
        }
      ],
      "history": [
        {
          "action": "submitted",
          "by": "production_partner",
          "round": 1,
          "note": null,
          "at": "2026-10-10T09:01:00Z"
        }
      ]
    }
  ]
}

Get a QC ticket

GET/v2/qc-tickets/{id}

Credentials
An API key with the qc:read scope (full access includes every scope). A signed-in dashboard session works too.
For
Designers
Operation id
get_qc_ticket

Returns one ticket with every product on it, its photos and the history of decisions and messages. Designers only.

Path parameters

  • idstringRequired

    Unique identifier of the qc ticket, prefixed with qct_. Treat it as an opaque string: store it and compare it, never parse it.

Returns200 OK

The ticket. Returns the QC ticket object.

Responses

  • 200

    The ticket.

  • 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 QC ticket with this id (qc_ticket_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.

GET/v2/qc-tickets/{id}
curl "https://public-api.dapi.digital/v2/qc-tickets/qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
Response
{
  "id": "qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "qc_ticket",
  "kind": "quality_check",
  "reason": "spot_check",
  "status": "awaiting_your_review",
  "needs_your_action": true,
  "blocks_shipping": true,
  "order": {
    "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "order",
    "name": "PV-SCN-1701A372"
  },
  "round": 1,
  "replacement": null,
  "created_at": "2026-10-09T15:20:00Z",
  "submitted_at": "2026-10-10T09:01:00Z",
  "resolved_at": null,
  "items": [
    {
      "id": "qci_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "qc_item",
      "title": "Clicker Blue",
      "product": {
        "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "product",
        "name": "Clicker"
      },
      "variant": {
        "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "variant",
        "name": "Clicker Blue"
      },
      "status": "in_review",
      "round": 1,
      "photos": [
        {
          "id": "qcp_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "url": "https://files.example.com/qc/clicker-front.jpg?signature=abc",
          "round": 1,
          "uploaded_at": "2026-10-10T09:00:00Z"
        }
      ],
      "history": [
        {
          "action": "submitted",
          "by": "production_partner",
          "round": 1,
          "note": null,
          "at": "2026-10-10T09:01:00Z"
        }
      ]
    }
  ]
}