Extras
Bought-in parts shipped with products (designers) or with order lines (integrators).
The extra object
A bought-in part that ships with a product, for example a magnet, screw or battery. Extras belong to the account (designer or integrator) that created them and are reviewed by 3DAPI before use.
Attributes
- idstring
Unique identifier of the extra, prefixed with
xtr_. Treat it as an opaque string: store it and compare it, never parse it. - objectstring
Names the type of this object. Always
extra. - namestring
Display name of the part.
- descriptionstring
What the part is, as written by the seller.
- reviewReview
Review by 3DAPI. Anything that will be manufactured is reviewed before it can be sold or produced.
- priceobject or null
What one unit adds to a variant cost, under
costs.extra_parts(designers), or to the production price of each printed unit of an order line that carries it (integrators,items[].extras). Set by 3DAPI when the extra is approved. While an edited extra is back in review this stays the last approved price. Null until the extra is approved, and null again after a rejection: rejecting clears the price even if the extra was approved before. - purchase_linksarray of object
Where the production partner can buy the part. At least one.
- imagesarray of object
Photos of the part. At least one.
- created_attimestamp
Point in time, ISO 8601 in UTC.
- updated_attimestamp
When the extra last changed: created, edited, or approved or rejected by 3DAPI. Extras last changed before this was recorded show the latest of their creation and review decision instead.
{
"id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
"object": "extra",
"name": "Magnet 10mm",
"description": "Neodymium magnet, 10mm diameter.",
"review": {
"status": "approved",
"reason": null,
"reviewed_at": "2026-07-22T10:05:00Z"
},
"price": {
"amount": 50,
"currency": "usd"
},
"purchase_links": [
{
"url": "https://example.com/magnet-10mm",
"label": "Datasheet"
}
],
"images": [
{
"url": "https://cdn.example.com/magnet.png",
"name": "magnet.png"
}
],
"created_at": "2026-07-22T09:56:30Z",
"updated_at": "2026-07-22T10:05:00Z"
}List extra parts
GET/
- Credentials
- An API key with the
extras:readscope (full access includes every scope). A signed-in dashboard session works too. - For
- Designers and Integrators
- Operation id
list_extras
Lists your extra parts, newest first, with their review status and price. For designers and integrators.
Query parameters
- review_statusarray of ReviewStatus
Only extras in these review states.
Possible valuespendingapprovedrejected - searchstring
Only extras whose name contains this text, ignoring case.
- sortstring
Order of results. A leading
-means descending.namesorts alphabetically in a language-aware collation, which compares letters before spaces and case (test slicercomes aftertesting warnings); do not expect byte order.Possible values-created_atcreated_atname - 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 extras.
Responses
- 200
A page of extras.
- 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/extras" \
-H "x-api-key: $DAPI_API_KEY"{
"object": "list",
"data": [
{
"id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
"object": "extra",
"name": "Magnet 10mm",
"description": "Neodymium magnet, 10mm diameter.",
"review": {
"status": "approved",
"reason": null,
"reviewed_at": "2026-07-22T10:05:00Z"
},
"price": {
"amount": 50,
"currency": "usd"
},
"purchase_links": [
{
"url": "https://example.com/magnet-10mm",
"label": "Datasheet"
}
],
"images": [
{
"url": "https://cdn.example.com/magnet.png",
"name": "magnet.png"
}
],
"created_at": "2026-07-22T09:56:30Z",
"updated_at": "2026-07-22T10:05:00Z"
}
],
"has_more": false,
"next_cursor": null
}Create an extra part
POST/
- Credentials
- An API key with the
extras:writescope (full access includes every scope). A signed-in dashboard session works too. - For
- Designers and Integrators
- Operation id
create_extra
Adds an extra part and submits it for review by 3DAPI. It comes back with review.status: pending and no price; we set the price when we approve it, and only approved extras can be used: in variants (designers) or on order lines (integrators, items[].extras of create_integrator_order). Upload the photos first with purpose extra_image. For designers and integrators.
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
- namestringRequired
Display name of the part.
- descriptionstringRequired
What the part is, for the production partner and our review.
- purchase_linksarray of objectRequired
Where the production partner can buy the part. At least one.
- imagesarray of objectsRequired
Photos of the part, at least one. Replaces the whole list on update.
Returns201 Created
The extra, pending review. Returns the extra object.
Responses
- 201
The extra, pending review.
- 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 casedetails.fix_urllinks to the dashboard page that fixes it. - 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), an imageurlis given (a new extra has no photos to keep,unknown_image), or the same upload is listed twice (duplicate_image). - 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/extras" \
-H "x-api-key: $DAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Magnet 10mm",
"description": "Neodymium magnet, 10mm diameter.",
"purchase_links": [
{
"url": "https://example.com/magnet-10mm",
"label": "Store"
}
],
"images": [
{
"upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
}
]
}'{
"id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
"object": "extra",
"name": "Magnet 10mm",
"description": "Neodymium magnet, 10mm diameter.",
"review": {
"status": "pending",
"reason": null,
"reviewed_at": null
},
"price": null,
"purchase_links": [
{
"url": "https://example.com/magnet-10mm",
"label": "Datasheet"
}
],
"images": [
{
"url": "https://cdn.example.com/magnet.png",
"name": "magnet.png"
}
],
"created_at": "2026-07-22T09:56:30Z",
"updated_at": "2026-07-22T10:05:00Z"
}Get an extra part
GET/
- Credentials
- An API key with the
extras:readscope (full access includes every scope). A signed-in dashboard session works too. - For
- Designers and Integrators
- Operation id
get_extra
Returns one of your extra parts with its review status and price. For designers and integrators.
Path parameters
- idstringRequired
Unique identifier of the extra, prefixed with
xtr_. Treat it as an opaque string: store it and compare it, never parse it.
Returns200 OK
The extra. Returns the extra object.
Responses
- 200
The extra.
- 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 extra with this id (
extra_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/extras/xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
-H "x-api-key: $DAPI_API_KEY"{
"id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
"object": "extra",
"name": "Magnet 10mm",
"description": "Neodymium magnet, 10mm diameter.",
"review": {
"status": "approved",
"reason": null,
"reviewed_at": "2026-07-22T10:05:00Z"
},
"price": {
"amount": 50,
"currency": "usd"
},
"purchase_links": [
{
"url": "https://example.com/magnet-10mm",
"label": "Datasheet"
}
],
"images": [
{
"url": "https://cdn.example.com/magnet.png",
"name": "magnet.png"
}
],
"created_at": "2026-07-22T09:56:30Z",
"updated_at": "2026-07-22T10:05:00Z"
}Update an extra part
PATCH/
- Credentials
- An API key with the
extras:writescope (full access includes every scope). A signed-in dashboard session works too. - For
- Designers and Integrators
- Operation id
update_extra
Changes the fields you send. Sending any field sends the extra back to review by 3DAPI (review.status: pending), even if the value is unchanged; a body with no fields changes nothing. Until it is approved again the extra keeps its last approved price. For designers, every product using it goes back to review too (pending), which takes it off your store until 3DAPI approves the extra again, exactly as an edit in the dashboard does. For integrators, new orders cannot use it until it is approved again; orders already placed keep it. For designers and integrators.
Path parameters
- idstringRequired
Unique identifier of the extra, prefixed with
xtr_. Treat it as an opaque string: store it and compare it, never parse it.
Request body
- namestring
Display name of the part.
- descriptionstring
What the part is, for the production partner and our review.
- purchase_linksarray of object
Where the production partner can buy the part. At least one.
- imagesarray of objects
Photos of the part, at least one. Replaces the whole list on update.
Returns200 OK
The extra, pending review. Returns the extra object.
Responses
- 200
The extra, pending review.
- 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 casedetails.fix_urllinks to the dashboard page that fixes it. - 404
You have no extra with this id (
extra_not_found). - 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), an imageurlis not one of the photos this extra has (unknown_image), or a photo is listed twice (duplicate_image). - 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 PATCH "https://public-api.dapi.digital/v2/extras/xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
-H "x-api-key: $DAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Neodymium magnet, 10mm diameter, N52 grade.",
"images": [
{
"url": "https://cdn.example.com/magnet.png"
},
{
"upload": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
}
]
}'{
"id": "xtr_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
"object": "extra",
"name": "Magnet 10mm",
"description": "Neodymium magnet, 10mm diameter.",
"review": {
"status": "pending",
"reason": null,
"reviewed_at": null
},
"price": {
"amount": 50,
"currency": "usd"
},
"purchase_links": [
{
"url": "https://example.com/magnet-10mm",
"label": "Datasheet"
}
],
"images": [
{
"url": "https://cdn.example.com/magnet.png",
"name": "magnet.png"
}
],
"created_at": "2026-07-22T09:56:30Z",
"updated_at": "2026-07-22T10:05:00Z"
}