Attributes
Paste a key and every snippet on this page switches from the placeholder to your key — and the Try it panel under each endpoint is ready to send. It is stored in this browser only and goes nowhere except, if you press Send, straight to the API host you pick there.
List attributes
/ext/v1/attributesproducts:readEvery attribute with its values[]. search resolves a name such as "Colour" to its id; type keeps only variant or specification attributes.
Query parameters
| Name | Type | Description |
|---|---|---|
limit | numberdefault 25max 100 | Page size. |
starting_after | string | Return the page AFTER this object id (the previous page's next_cursor). |
ending_before | string | Return the page BEFORE this object id. |
sort | stringdefault -created_at | Newest first by default. Only created_at is sortable — a cursor over a mutable key (like updated_at) cannot page reliably. Use updated_after to sync changes.created_at-created_at |
store_id | string | Restrict to one store. Required breadth control for company-scoped keys; on a store-scoped key it must match the key's own store. |
search | string | Match on name or slug (case-insensitive, partial) — how a name resolves to an id. |
type | string | Only variant or only specification attributes.variantspecification |
Request
export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"
curl -sS "https://api.shopdeskbd.com/ext/v1/attributes?limit=25" \
-H "Authorization: Bearer $SHOPDESK_API_KEY"const url = new URL("https://api.shopdeskbd.com/ext/v1/attributes");
Object.entries({
limit: "25",
}).forEach(([key, value]) => url.searchParams.set(key, value));
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.SHOPDESK_API_KEY}`,
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const { data, meta } = await response.json();import os
import requests
response = requests.get(
"https://api.shopdeskbd.com/ext/v1/attributes",
params={
"limit": "25",
},
headers={
"Authorization": f"Bearer {os.environ['SHOPDESK_API_KEY']}",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
page = response.json()
rows, meta = page["data"], page["meta"]<?php
$key = getenv('SHOPDESK_API_KEY');
$ch = curl_init('https://api.shopdeskbd.com/ext/v1/attributes?limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key],
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}
$rows = $response['data'];
$meta = $response['meta'];https://api.shopdeskbd.com/ext/v1/attributes?search=colourStored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Query parameters
Blank fields are left out of the request.
Paste a key above to enable Send.
Responses
200A cursor page of attributes.401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.422Validation failed —error.fieldsmaps each offending field to its messages.429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"data": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Colour",
"slug": "colour",
"type": "variant",
"input_type": "color",
"is_filterable": true,
"is_required": false,
"values": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"attribute_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"value": "Red",
"position": 0,
"swatch_hex": "#FF0000",
"swatch_image_url": "https://cdn.example.com/companies/c1/attributes/swatches/swatch.webp",
"created_at": "2026-08-11T10:00:00.000Z"
}
],
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}
],
"meta": {
"has_more": true,
"next_cursor": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001"
}
}Create an attribute
/ext/v1/attributescatalog:writeCreates an attribute, optionally with its first values. A swatch tile sent as swatch_image_url is downloaded and stored by Shopdesk before anything is written. Image links: https only, PNG/JPEG/WebP/GIF read from the bytes (never SVG), at most 10 MB and 8000 px on the longest side (and 40 megapixels in all; 16 for a GIF), fetched within 10 s each and 30 s for the call, at most 50 MB downloaded per call. One failing link fails the whole call and nothing is written. Each image a call carries — link or file — counts as one write against the key's per-minute write limit; calls that download or upload images run at most 2 at a time per key; and a company may have 2,000 links downloaded per UTC day. Past any of these the answer is 429 RATE_LIMITED with Retry-After.
Headers
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional, but honored: send it to make retries of this request safe. |
Body parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Target store. REQUIRED with a company-scoped key; with a store-scoped key it may only repeat the key's own store. |
namerequired | string | — |
slug | string | Lowercase letters, digits and single hyphens (anything else is refused with 422). Derived from the name when omitted, and suffixed when taken. |
typerequired | string | variant attributes make variants (Colour, Size); specification attributes only describe a product (Material). It can be changed later only while no product uses the attribute.variantspecification |
input_type | stringdefault text | How values are shown: text, color (uses swatch_hex) or image (uses swatch_image_url). Free to change later.textcolorimage |
is_filterable | booleandefault false | Offer it as a storefront filter. |
is_required | booleandefault false | — |
values | AttributeValueInputDto[] | Initial values, in order. A value repeated (ignoring case) is refused with 409. |
values[] fields
| Name | Type | Description |
|---|---|---|
valuerequired | string | — |
position | integer | Display order, lowest first. Inline values default to their place in the list; a value added later defaults to 0. |
swatch_hex | string | Swatch colour, #RRGGBB (stored upper-case). Only on an attribute whose input_type is color or image; sent to a text attribute it is refused with 422, never dropped. |
swatch_image_url | string | A swatch tile for patterns and fabrics, by https link. The server downloads it, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB and 8000 px on the longest side) and stores a copy; the link itself is never kept. A link that fails fails the whole call with 422 naming this field, and nothing is written. At most 10 links per call. Only on an attribute whose input_type is color or image; sent to a text attribute it is refused with 422, never dropped. |
Request
export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"
curl -sS -X POST "https://api.shopdeskbd.com/ext/v1/attributes" \
-H "Authorization: Bearer $SHOPDESK_API_KEY" \
-H "Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47" \
-H "Content-Type: application/json" \
-d '{
"name": "Colour",
"type": "variant",
"values": [
{
"value": "Red"
},
{
"value": "Blue"
}
]
}'const url = new URL("https://api.shopdeskbd.com/ext/v1/attributes");
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SHOPDESK_API_KEY}`,
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Colour",
"type": "variant",
"values": [
{
"value": "Red"
},
{
"value": "Blue"
}
]
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
payload = {
"name": "Colour",
"type": "variant",
"values": [
{
"value": "Red",
},
{
"value": "Blue",
},
],
}
response = requests.post(
"https://api.shopdeskbd.com/ext/v1/attributes",
json=payload,
headers={
"Authorization": f"Bearer {os.environ['SHOPDESK_API_KEY']}",
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SHOPDESK_API_KEY');
$payload = json_encode([
'name' => 'Colour',
'type' => 'variant',
'values' => [
[
'value' => 'Red',
],
[
'value' => 'Blue',
],
],
]);
$ch = curl_init('https://api.shopdeskbd.com/ext/v1/attributes');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.shopdeskbd.com/ext/v1/attributesStored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
generating…A new value is generated whenever you edit this request, and kept while you do not — so sending twice without changing anything is a real retry and comes back Idempotent-Replayed: true instead of writing again.
Paste a key above to enable Send.
Responses
201The new attribute with its values.400AnIdempotency-Keyheader was sent but is empty or longer than 255 characters (VALIDATION_ERROR;error.fieldsnames the header).401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.409Two values are the same, ignoring case (CONFLICT,error.fields.values); or a request with thisIdempotency-Keyis still in flight (IDEMPOTENCY_IN_FLIGHT) — retry once it has finished.422Validation failed, or a swatch on atextattribute. Aswatch_image_urlthat cannot be downloaded or is not an allowed image is refused here witherror.fieldsnaming it (values[2].swatch_image_urlon create).429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Colour",
"slug": "colour",
"type": "variant",
"input_type": "color",
"is_filterable": true,
"is_required": false,
"values": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"attribute_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"value": "Red",
"position": 0,
"swatch_hex": "#FF0000",
"swatch_image_url": "https://cdn.example.com/companies/c1/attributes/swatches/swatch.webp",
"created_at": "2026-08-11T10:00:00.000Z"
}
],
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}Get an attribute by id
/ext/v1/attributes/{id}products:readOne attribute with every value, in display order.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The attribute id. |
Query parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Restrict the lookup to one store. Breadth control for company-scoped keys; on a store-scoped key it must match the key's own store. |
Request
export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"
curl -sS "https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID" \
-H "Authorization: Bearer $SHOPDESK_API_KEY"const url = new URL("https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.SHOPDESK_API_KEY}`,
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
response = requests.get(
"https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID",
headers={
"Authorization": f"Bearer {os.environ['SHOPDESK_API_KEY']}",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SHOPDESK_API_KEY');
$ch = curl_init('https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key],
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.shopdeskbd.com/ext/v1/attributes/5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001Stored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Path parameters
Query parameters
Blank fields are left out of the request.
Paste a key above to enable Send.
Responses
200The attribute.401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.404Attribute not found.422Validation failed —error.fieldsmaps each offending field to its messages.429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Colour",
"slug": "colour",
"type": "variant",
"input_type": "color",
"is_filterable": true,
"is_required": false,
"values": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"attribute_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"value": "Red",
"position": 0,
"swatch_hex": "#FF0000",
"swatch_image_url": "https://cdn.example.com/companies/c1/attributes/swatches/swatch.webp",
"created_at": "2026-08-11T10:00:00.000Z"
}
],
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}Update an attribute
/ext/v1/attributes/{id}catalog:writeRenames it or changes its slug, input_type or flags. type can change only while no product variant or specification uses the attribute. Values are added and edited through /attributes/{id}/values. Fields left out are left alone.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The attribute id. |
Headers
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional, but honored: send it to make retries of this request safe. |
Body parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Target store. REQUIRED with a company-scoped key; with a store-scoped key it may only repeat the key's own store. |
name | string | — |
slug | string | A rename keeps the slug; send one to change it. |
type | string | Refused with 409 while any product variant or specification uses the attribute — create a new attribute instead.variantspecification |
input_type | string | Changes only how values are shown. Switching back to text keeps the stored swatches, hidden.textcolorimage |
is_filterable | boolean | — |
is_required | boolean | — |
Request
export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"
curl -sS -X PATCH "https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID" \
-H "Authorization: Bearer $SHOPDESK_API_KEY" \
-H "Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47" \
-H "Content-Type: application/json" \
-d '{
"name": "Colour",
"is_filterable": true
}'const url = new URL("https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID");
const response = await fetch(url, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SHOPDESK_API_KEY}`,
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Colour",
"is_filterable": true
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
payload = {
"name": "Colour",
"is_filterable": True,
}
response = requests.patch(
"https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID",
json=payload,
headers={
"Authorization": f"Bearer {os.environ['SHOPDESK_API_KEY']}",
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SHOPDESK_API_KEY');
$payload = json_encode([
'name' => 'Colour',
'is_filterable' => true,
]);
$ch = curl_init('https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.shopdeskbd.com/ext/v1/attributes/5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001Stored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Path parameters
generating…A new value is generated whenever you edit this request, and kept while you do not — so sending twice without changing anything is a real retry and comes back Idempotent-Replayed: true instead of writing again.
Paste a key above to enable Send.
Responses
200The updated attribute with its values.400AnIdempotency-Keyheader was sent but is empty or longer than 255 characters (VALIDATION_ERROR;error.fieldsnames the header).401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.404Attribute not found.409typecannot change while products use the attribute (CONFLICT,error.fields.type); or a request with thisIdempotency-Keyis still in flight (IDEMPOTENCY_IN_FLIGHT) — retry once it has finished.422Validation failed —error.fieldsmaps each offending field to its messages.429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"store_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"name": "Colour",
"slug": "colour",
"type": "variant",
"input_type": "color",
"is_filterable": true,
"is_required": false,
"values": [
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"attribute_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"value": "Red",
"position": 0,
"swatch_hex": "#FF0000",
"swatch_image_url": "https://cdn.example.com/companies/c1/attributes/swatches/swatch.webp",
"created_at": "2026-08-11T10:00:00.000Z"
}
],
"created_at": "2026-08-11T10:00:00.000Z",
"updated_at": "2026-08-11T10:00:00.000Z"
}Add a value to an attribute
/ext/v1/attributes/{id}/valuescatalog:writeAdds one value, such as "XL" to Size. A swatch_image_url is downloaded and stored by Shopdesk before the value is written. Image links: https only, PNG/JPEG/WebP/GIF read from the bytes (never SVG), at most 10 MB and 8000 px on the longest side (and 40 megapixels in all; 16 for a GIF), fetched within 10 s each and 30 s for the call, at most 50 MB downloaded per call. One failing link fails the whole call and nothing is written. Each image a call carries — link or file — counts as one write against the key's per-minute write limit; calls that download or upload images run at most 2 at a time per key; and a company may have 2,000 links downloaded per UTC day. Past any of these the answer is 429 RATE_LIMITED with Retry-After.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The attribute id. |
Headers
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional, but honored: send it to make retries of this request safe. |
Body parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Target store. REQUIRED with a company-scoped key; with a store-scoped key it may only repeat the key's own store. |
valuerequired | string | — |
position | integer | Display order, lowest first. Defaults to 0. |
swatch_hex | string | Swatch colour, #RRGGBB (stored upper-case). Only on an attribute whose input_type is color or image; sent to a text attribute it is refused with 422, never dropped. |
swatch_image_url | string | A swatch tile for patterns and fabrics, by https link. The server downloads it, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB and 8000 px on the longest side) and stores a copy; the link itself is never kept. A link that fails fails the whole call with 422 naming this field, and nothing is written. At most 10 links per call. Only on an attribute whose input_type is color or image; sent to a text attribute it is refused with 422, never dropped. |
Request
export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"
curl -sS -X POST "https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values" \
-H "Authorization: Bearer $SHOPDESK_API_KEY" \
-H "Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47" \
-H "Content-Type: application/json" \
-d '{
"value": "XL"
}'const url = new URL("https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values");
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SHOPDESK_API_KEY}`,
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
"Content-Type": "application/json",
},
body: JSON.stringify({
"value": "XL"
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
payload = {
"value": "XL",
}
response = requests.post(
"https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values",
json=payload,
headers={
"Authorization": f"Bearer {os.environ['SHOPDESK_API_KEY']}",
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SHOPDESK_API_KEY');
$payload = json_encode([
'value' => 'XL',
]);
$ch = curl_init('https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.shopdeskbd.com/ext/v1/attributes/5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001/valuesStored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Path parameters
generating…A new value is generated whenever you edit this request, and kept while you do not — so sending twice without changing anything is a real retry and comes back Idempotent-Replayed: true instead of writing again.
Paste a key above to enable Send.
Responses
201The new value.400AnIdempotency-Keyheader was sent but is empty or longer than 255 characters (VALIDATION_ERROR;error.fieldsnames the header).401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.404Attribute not found.409The attribute already has this value (CONFLICT,error.fields.value); or a request with thisIdempotency-Keyis still in flight (IDEMPOTENCY_IN_FLIGHT) — retry once it has finished.422Validation failed, or a swatch on atextattribute. Aswatch_image_urlthat cannot be downloaded or is not an allowed image is refused here witherror.fieldsnaming it (values[2].swatch_image_urlon create).429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"attribute_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"value": "Red",
"position": 0,
"swatch_hex": "#FF0000",
"swatch_image_url": "https://cdn.example.com/companies/c1/attributes/swatches/swatch.webp",
"created_at": "2026-08-11T10:00:00.000Z"
}Update an attribute value
/ext/v1/attributes/{id}/values/{valueId}catalog:writeRenames a value, moves it, or sets or clears its swatch. Renaming relabels every variant and product that shows it. A new swatch_image_url is downloaded and stored by Shopdesk. Image links: https only, PNG/JPEG/WebP/GIF read from the bytes (never SVG), at most 10 MB and 8000 px on the longest side (and 40 megapixels in all; 16 for a GIF), fetched within 10 s each and 30 s for the call, at most 50 MB downloaded per call. One failing link fails the whole call and nothing is written. Each image a call carries — link or file — counts as one write against the key's per-minute write limit; calls that download or upload images run at most 2 at a time per key; and a company may have 2,000 links downloaded per UTC day. Past any of these the answer is 429 RATE_LIMITED with Retry-After.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The attribute id. |
valueIdrequired | uuid | The value id, as listed in the attribute's values[]. |
Headers
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional, but honored: send it to make retries of this request safe. |
Body parameters
| Name | Type | Description |
|---|---|---|
store_id | uuid | Target store. REQUIRED with a company-scoped key; with a store-scoped key it may only repeat the key's own store. |
value | string | Renaming relabels every variant and product that shows this value. |
position | integer | — |
swatch_hex | string | Swatch colour, #RRGGBB; null clears it. Only on an attribute whose input_type is color or image; sent to a text attribute it is refused with 422, never dropped. |
swatch_image_url | string | A swatch tile for patterns and fabrics, by https link. The server downloads it, checks it is a PNG, JPEG, WebP or GIF (at most 10 MB and 8000 px on the longest side) and stores a copy; the link itself is never kept. A link that fails fails the whole call with 422 naming this field, and nothing is written. At most 10 links per call. null clears it. Only on an attribute whose input_type is color or image; sent to a text attribute it is refused with 422, never dropped. |
Request
export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"
curl -sS -X PATCH "https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values/REPLACE_WITH_ID" \
-H "Authorization: Bearer $SHOPDESK_API_KEY" \
-H "Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47" \
-H "Content-Type: application/json" \
-d '{
"value": "Extra large",
"position": 4
}'const url = new URL("https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values/REPLACE_WITH_ID");
const response = await fetch(url, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SHOPDESK_API_KEY}`,
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
"Content-Type": "application/json",
},
body: JSON.stringify({
"value": "Extra large",
"position": 4
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} [${error.request_id}]`);
}
const object = await response.json();import os
import requests
payload = {
"value": "Extra large",
"position": 4,
}
response = requests.patch(
"https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values/REPLACE_WITH_ID",
json=payload,
headers={
"Authorization": f"Bearer {os.environ['SHOPDESK_API_KEY']}",
"Idempotency-Key": "3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47",
},
timeout=30,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
obj = response.json()<?php
$key = getenv('SHOPDESK_API_KEY');
$payload = json_encode([
'value' => 'Extra large',
'position' => 4,
]);
$ch = curl_init('https://api.shopdeskbd.com/ext/v1/attributes/REPLACE_WITH_ID/values/REPLACE_WITH_ID');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Idempotency-Key: 3f2b8c1e-9d4a-4e7b-8a6f-2c5d1e0b9a47', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$response = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("{$response['error']['code']}: {$response['error']['message']}");
}https://api.shopdeskbd.com/ext/v1/attributes/5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001/values/9c1e4b2a-7d3f-4e58-b0a1-2f6d8c9e1a45Stored in this browser only and never sent anywhere except to the API host you picked above — these pages are static files with no server behind them.
Path parameters
generating…A new value is generated whenever you edit this request, and kept while you do not — so sending twice without changing anything is a real retry and comes back Idempotent-Replayed: true instead of writing again.
Paste a key above to enable Send.
Responses
200The updated value.400AnIdempotency-Keyheader was sent but is empty or longer than 255 characters (VALIDATION_ERROR;error.fieldsnames the header).401Missing/invalid API key, revoked or expired key, plan without API access, or a dead store.403The key's scopes do not cover this endpoint.404Attribute not found, or the value is not on it.409Another value of the attribute already has this name (CONFLICT,error.fields.value); or a request with thisIdempotency-Keyis still in flight (IDEMPOTENCY_IN_FLIGHT) — retry once it has finished.422Validation failed, or a swatch on atextattribute. Aswatch_image_urlthat cannot be downloaded or is not an allowed image is refused here witherror.fieldsnaming it (values[2].swatch_image_urlon create).429Rate limit exceeded for this key. HonorRetry-Afterand theX-RateLimit-*headers.
Every failure uses the one error envelope — Errors lists each code and what to do with it.
{
"id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"attribute_id": "5f7d2f60-0d1c-4b3a-9a68-6f4d21f6a001",
"value": "Red",
"position": 0,
"swatch_hex": "#FF0000",
"swatch_image_url": "https://cdn.example.com/companies/c1/attributes/swatches/swatch.webp",
"created_at": "2026-08-11T10:00:00.000Z"
}