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.
- 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.
{
"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"
}
]
}
]
}Review a quality check
POST/
- Credentials
- An API key with the
qc:writescope (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.errorslists each problem (validation_failed), or anexpand[]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 casedetails.fix_urllinks 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.fieldnames 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
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"
]
}
]
}'{
"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/
- Credentials
- An API key with the
qc:writescope (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.errorslists each problem (validation_failed), or anexpand[]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 casedetails.fix_urllinks 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_missingwhen the file was never sent,upload_already_usedwhen 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
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"
}
]
}'{
"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/
- Credentials
- An API key with the
qc:readscope (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 - limitinteger
How many results per page, from 1 to 100.
- cursorstring
Where to continue from: the
next_cursorof 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.errorslists each problem (validation_failed), or anexpand[]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 casedetails.fix_urllinks 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
curl "https://public-api.dapi.digital/v2/qc-tickets" \
-H "x-api-key: $DAPI_API_KEY"{
"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/
- Credentials
- An API key with the
qc:writescope (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.errorslists each problem (validation_failed), or anexpand[]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 casedetails.fix_urllinks 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.fieldnames 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_missingwhen the file was never sent,upload_already_usedwhen 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
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"
}
]
}'{
"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/
- Credentials
- An API key with the
qc:readscope (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 casedetails.fix_urllinks 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
curl "https://public-api.dapi.digital/v2/qc-tickets/qct_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
-H "x-api-key: $DAPI_API_KEY"{
"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"
}
]
}
]
}