Product types API
Every Product types action (14) and data model (8): endpoints, scopes, input, preconditions, events and examples.
Product types and reusable attributes such as colour, size and material.
Module code attributes · 14 actions · 8 data models · 10 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: attributes (everything), attributes:read (read only) or attributes:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
attribute_item_options | attribute_item_option | /api/v1/attribute_item_options | |||
attribute_values | attribute_value | /api/v1/attribute_values | attributes.save_attribute_value | attributes.delete_attribute_value | |
attributes | attribute | /api/v1/attributes | attributes.save_attribute | attributes.delete_attribute | |
product_attribute_values | item_attribute_value | /api/v1/product_attribute_values | |||
product_type_attributes | product_type_attribute | /api/v1/product_type_attributes | |||
product_type_links | item_product_type | /api/v1/product_type_links | |||
product_types | product_type | /api/v1/product_types | attributes.save_product_type | attributes.delete_product_type | |
variant_attribute_values | variant_attribute_value | /api/v1/variant_attribute_values |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
attributes.assign_attributes | attributes:manage | Add attributes to a type |
attributes.delete_attribute | attributes:delete | Delete an attribute |
attributes.delete_attribute_value | attributes:manage | Remove a choice |
attributes.delete_product_type | attributes:delete | Delete a product type |
attributes.reorder_attribute_values | attributes:manage | Reorder choices |
attributes.reorder_type_attributes | attributes:manage | Reorder a type’s attributes |
attributes.save_attribute | attributes:manage | Save an attribute |
attributes.save_attribute_value | attributes:manage | Save a choice |
attributes.save_product_type | attributes:manage | Save a product type |
attributes.set_item_attributes | attributes:write | Fill in an item’s attributes |
attributes.set_item_type | attributes:write | Give an item its product type |
attributes.set_variant_attributes | attributes:write | Fill in a variant’s attributes |
attributes.unassign_attribute | attributes:manage | Remove an attribute from a type |
attributes.update_assignment | attributes:manage | Switch the variant picker |
attributes.assign_attributes
Add attributes to a type — At product level, or at variant level — optionally driving the variant picker.
Needs attributes:manage · changes data
https://app.getera.io/api/v1/attribute_item_options/actions/assign_attributes- Canonical address:
POST /api/v1/actions/attributes.assign_attributes
Input
| Field | Type | Required | Notes |
|---|---|---|---|
productTypeId | uuid | yes | Product type · id of product_type |
attributeIds | object / array | yes | Attributes |
level | string (enum) | yes | Level · Allowed values: product, variant |
variantSelection | boolean | Drives the variant picker |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The type and every attribute must exist. |
attribute_already_assigned | An attribute sits on a type once, at one level. |
variant_only_attribute | A variant-only attribute cannot go on the product. |
variant_selection_not_allowed | Only variant-level dropdown, yes/no, swatch or number attributes drive the picker. |
Events it publishes: attributes.type_attributes_changed, attributes.variant_axes_synced
What it writes: attribute_item_option (upsert) — Maps a picker attribute onto the catalogue axis (item.md §9b); product_type_attribute (insert) — Assigns the attributes
curl -X POST https://app.getera.io/api/v1/attribute_item_options/actions/assign_attributes \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"productTypeId":"PRODUCT_TYPE_ID","attributeIds":{},"level":"product"}'{
"ok": true,
"result": "…",
"events": [
"attributes.type_attributes_changed",
"attributes.variant_axes_synced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributeItemOptions.assignAttributes({
productTypeId: "PRODUCT_TYPE_ID",
attributeIds: {},
level: "product"
})era attribute_item_options assign_attributes --data '{"productTypeId":"PRODUCT_TYPE_ID","attributeIds":{},"level":"product"}'{
"name": "era__attributes__assign_attributes",
"arguments": {
"productTypeId": "PRODUCT_TYPE_ID",
"attributeIds": {},
"level": "product"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, attribute_already_assigned, variant_only_attribute, variant_selection_not_allowed · 429 slow down
attributes.delete_attribute
Delete an attribute — Its choices, its assignments and every product’s value for it go with it.
Needs attributes:delete · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/attributes/{id}/actions/delete_attribute- REST alias:
DELETE /api/v1/attributes/{id} - Canonical address:
POST /api/v1/actions/attributes.delete_attribute
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Attribute · id of attribute |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The attribute must exist. |
Events it publishes: attributes.attribute_deleted
What it writes: attribute (delete) — Removes it; the FKs remove what hangs off it
curl -X POST https://app.getera.io/api/v1/attributes/ATTRIBUTE_ID/actions/delete_attribute \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"attributes.attribute_deleted"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributes.deleteAttribute("ATTRIBUTE_ID")era attributes delete_attribute ATTRIBUTE_ID{
"name": "era__attributes__delete_attribute",
"arguments": {
"id": "ATTRIBUTE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
attributes.delete_attribute_value
Remove a choice — Products that had it chosen lose that value.
Needs attributes:manage · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/attribute_values/{id}/actions/delete_attribute_value- REST alias:
DELETE /api/v1/attribute_values/{id} - Canonical address:
POST /api/v1/actions/attributes.delete_attribute_value
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Choice · id of attribute_value |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The choice must exist. |
Events it publishes: attributes.attribute_values_changed
What it writes: attribute_value (delete) — Removes the choice
curl -X POST https://app.getera.io/api/v1/attribute_values/ATTRIBUTE_VALUE_ID/actions/delete_attribute_value \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"attributes.attribute_values_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributeValues.deleteAttributeValue("ATTRIBUTE_VALUE_ID")era attribute_values delete_attribute_value ATTRIBUTE_VALUE_ID{
"name": "era__attributes__delete_attribute_value",
"arguments": {
"id": "ATTRIBUTE_VALUE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
attributes.delete_product_type
Delete a product type — Only while no item is of the type. Saleor would delete the products too; this refuses.
Needs attributes:delete · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/product_types/{id}/actions/delete_product_type- REST alias:
DELETE /api/v1/product_types/{id} - Canonical address:
POST /api/v1/actions/attributes.delete_product_type
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Product type · id of product_type |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The type must exist. |
product_type_in_use | No item may be of the type. |
Events it publishes: attributes.product_type_deleted
What it writes: product_type (delete) — Removes the type and its assignments
curl -X POST https://app.getera.io/api/v1/product_types/PRODUCT_TYPE_ID/actions/delete_product_type \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"attributes.product_type_deleted"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productTypes.deleteProductType("PRODUCT_TYPE_ID")era product_types delete_product_type PRODUCT_TYPE_ID{
"name": "era__attributes__delete_product_type",
"arguments": {
"id": "PRODUCT_TYPE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, product_type_in_use · 429 slow down
attributes.reorder_attribute_values
Reorder choices — The order a picker lists them in.
Needs attributes:manage · changes data
https://app.getera.io/api/v1/attribute_values/actions/reorder_attribute_values- Canonical address:
POST /api/v1/actions/attributes.reorder_attribute_values
Input
| Field | Type | Required | Notes |
|---|---|---|---|
attributeId | uuid | yes | Attribute · id of attribute |
valueIds | object / array | yes | Choices |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The attribute must exist. |
value_not_of_attribute | Every choice must be this attribute’s. |
Events it publishes: attributes.attribute_values_changed
What it writes: attribute_value (update) — Rewrites sort_order
curl -X POST https://app.getera.io/api/v1/attribute_values/actions/reorder_attribute_values \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"attributeId":"ATTRIBUTE_ID","valueIds":{}}'{
"ok": true,
"result": "…",
"events": [
"attributes.attribute_values_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributeValues.reorderAttributeValues({
attributeId: "ATTRIBUTE_ID",
valueIds: {}
})era attribute_values reorder_attribute_values --data '{"attributeId":"ATTRIBUTE_ID","valueIds":{}}'{
"name": "era__attributes__reorder_attribute_values",
"arguments": {
"attributeId": "ATTRIBUTE_ID",
"valueIds": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, value_not_of_attribute · 429 slow down
attributes.reorder_type_attributes
Reorder a type’s attributes — The order the product and variant forms show them in.
Needs attributes:manage · changes data
https://app.getera.io/api/v1/product_type_attributes/actions/reorder_type_attributes- Canonical address:
POST /api/v1/actions/attributes.reorder_type_attributes
Input
| Field | Type | Required | Notes |
|---|---|---|---|
productTypeId | uuid | yes | Product type · id of product_type |
level | string (enum) | yes | Level · Allowed values: product, variant |
attributeIds | object / array | yes | Attributes |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The type must exist. |
attribute_not_on_type | Every attribute must be on the type at that level. |
Events it publishes: attributes.type_attributes_changed
What it writes: product_type_attribute (update) — Rewrites sort_order
curl -X POST https://app.getera.io/api/v1/product_type_attributes/actions/reorder_type_attributes \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"productTypeId":"PRODUCT_TYPE_ID","level":"product","attributeIds":{}}'{
"ok": true,
"result": "…",
"events": [
"attributes.type_attributes_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productTypeAttributes.reorderTypeAttributes({
productTypeId: "PRODUCT_TYPE_ID",
level: "product",
attributeIds: {}
})era product_type_attributes reorder_type_attributes --data '{"productTypeId":"PRODUCT_TYPE_ID","level":"product","attributeIds":{}}'{
"name": "era__attributes__reorder_type_attributes",
"arguments": {
"productTypeId": "PRODUCT_TYPE_ID",
"level": "product",
"attributeIds": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, attribute_not_on_type · 429 slow down
attributes.save_attribute
Save an attribute — A reusable property — colour, size, material — and how it is entered and shown.
Needs attributes:manage · changes data
https://app.getera.io/api/v1/attributes/{id}/actions/save_attribute- REST alias:
PATCH /api/v1/attributes/{id} - Canonical address:
POST /api/v1/actions/attributes.save_attribute
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | Filled from the path {id} · Attribute · id of attribute | |
name | string | yes | Name · max 255 |
slug | string | Code · max 250 | |
inputType | string (enum) | Type · Allowed values: dropdown, multiselect, file, reference, single-reference, numeric, rich-text, plain-text, swatch, boolean, date, date-time | |
entityType | string | Points at · max 40 | |
unit | string | Unit · max 40 | |
valueRequired | boolean | Required | |
isVariantOnly | boolean | Variants only | |
visibleInStorefront | boolean | Shown on the storefront | |
filterableInStorefront | boolean | Filter on the storefront | |
filterableInDashboard | boolean | Filter in lists | |
storefrontSearchPosition | number | Search position | |
availableInGrid | boolean | Column in lists | |
values | object / array | Choices |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invalid_name | The name must produce a usable code. |
not_found | An edited attribute must exist. |
input_type_locked | The type is chosen once. |
slug_taken | The code must be free in this workspace. |
entity_type_required | A reference must say what it points at. |
entity_type_not_allowed | Only a reference points at something, and only at a product, variant or category. |
unit_not_allowed | Only a number has a unit, and only a known one. |
property_not_allowed | Filtering, grid and search position only on types that support them. |
attribute_has_no_choices | Only dropdown, multi select and swatch have choices. |
value_name_taken | A choice may be given once. |
Events it publishes: attributes.attribute_saved
What it writes: attribute (upsert) — Creates or edits the attribute; attribute_value (insert) — Writes the first choices on create
curl -X POST https://app.getera.io/api/v1/attributes/ATTRIBUTE_ID/actions/save_attribute \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"attributes.attribute_saved"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributes.saveAttribute("ATTRIBUTE_ID", {
name: "Name"
})era attributes save_attribute ATTRIBUTE_ID \
--name "Name"{
"name": "era__attributes__save_attribute",
"arguments": {
"id": "ATTRIBUTE_ID",
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_name, not_found, input_type_locked, slug_taken, entity_type_required, entity_type_not_allowed, unit_not_allowed, property_not_allowed, attribute_has_no_choices, value_name_taken · 429 slow down
attributes.save_attribute_value
Save a choice — One option of a dropdown, multi select or swatch.
Needs attributes:manage · changes data
https://app.getera.io/api/v1/attribute_values/{id}/actions/save_attribute_value- REST alias:
PATCH /api/v1/attribute_values/{id} - Canonical address:
POST /api/v1/actions/attributes.save_attribute_value
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | Filled from the path {id} · Choice · id of attribute_value | |
attributeId | uuid | yes | Attribute · id of attribute |
name | string | yes | Name · max 250 |
value | string | Colour · #RRGGBB or #RRGGBBAA — swatches only. · max 9 | |
fileId | uuid | Picture · id of file |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The attribute and an edited choice must exist. |
attribute_has_no_choices | The attribute must be a dropdown, multi select or swatch. |
invalid_name | A choice needs a name. |
swatch_only_field | Only a swatch choice carries a colour or picture. |
invalid_swatch | A swatch is one valid colour or one picture. |
value_name_taken | Choice names are unique within the attribute, ignoring case. |
Events it publishes: attributes.attribute_values_changed, attributes.variant_axes_synced
What it writes: attribute_item_option (upsert) — Maps a picker attribute onto the catalogue axis (item.md §9b); attribute_value (upsert) — Creates or edits the choice
curl -X POST https://app.getera.io/api/v1/attribute_values/ATTRIBUTE_VALUE_ID/actions/save_attribute_value \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"attributeId":"ATTRIBUTE_ID","name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"attributes.attribute_values_changed",
"attributes.variant_axes_synced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributeValues.saveAttributeValue("ATTRIBUTE_VALUE_ID", {
attributeId: "ATTRIBUTE_ID",
name: "Name"
})era attribute_values save_attribute_value ATTRIBUTE_VALUE_ID \
--attribute-id "ATTRIBUTE_ID" \
--name "Name"{
"name": "era__attributes__save_attribute_value",
"arguments": {
"id": "ATTRIBUTE_VALUE_ID",
"attributeId": "ATTRIBUTE_ID",
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, attribute_has_no_choices, invalid_name, swatch_only_field, invalid_swatch, value_name_taken · 429 slow down
attributes.save_product_type
Save a product type — A kind of product, and the attribute set its products carry.
Needs attributes:manage · changes data
https://app.getera.io/api/v1/product_types/{id}/actions/save_product_type- REST alias:
PATCH /api/v1/product_types/{id} - Canonical address:
POST /api/v1/actions/attributes.save_product_type
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | Filled from the path {id} · Product type · id of product_type | |
name | string | yes | Name · max 250 |
slug | string | Address · max 255 | |
kind | string (enum) | Kind · Allowed values: normal, gift_card | |
isShippingRequired | boolean | Ships | |
weightGrams | number | Weight (grams) · min 0 | |
taxClassId | uuid | Tax class |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invalid_name | The name must produce a usable address. |
slug_taken | The address must be free in this workspace. |
not_found | An edited type must exist. |
product_type_in_use | The kind cannot change while items are of the type. |
Events it publishes: attributes.product_type_saved
What it writes: product_type (upsert) — Creates or edits the type
curl -X POST https://app.getera.io/api/v1/product_types/PRODUCT_TYPE_ID/actions/save_product_type \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"attributes.product_type_saved"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productTypes.saveProductType("PRODUCT_TYPE_ID", {
name: "Name"
})era product_types save_product_type PRODUCT_TYPE_ID \
--name "Name"{
"name": "era__attributes__save_product_type",
"arguments": {
"id": "PRODUCT_TYPE_ID",
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_name, slug_taken, not_found, product_type_in_use · 429 slow down
attributes.set_item_attributes
Fill in an item’s attributes — The product-level values — material, brand line, care instructions.
Needs attributes:write · changes data
https://app.getera.io/api/v1/product_attribute_values/actions/set_item_attributes- Canonical address:
POST /api/v1/actions/attributes.set_item_attributes
Input
| Field | Type | Required | Notes |
|---|---|---|---|
itemId | uuid | yes | Item · id of item |
values | object / array | yes | Values |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
item_not_found | The item must exist. |
item_has_no_type | The item must have a product type. |
attribute_not_on_type | Every attribute must be on the type at this level. |
attribute_value_invalid | A value must match its attribute’s type and choices. |
attribute_value_required | Required attributes must end up with a value. |
Events it publishes: attributes.item_attributes_changed
What it writes: item_attribute_value (upsert) — Replaces the values of each attribute sent
curl -X POST https://app.getera.io/api/v1/product_attribute_values/actions/set_item_attributes \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"itemId":"ITEM_ID","values":{}}'{
"ok": true,
"result": "…",
"events": [
"attributes.item_attributes_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productAttributeValues.setItemAttributes({
itemId: "ITEM_ID",
values: {}
})era product_attribute_values set_item_attributes --data '{"itemId":"ITEM_ID","values":{}}'{
"name": "era__attributes__set_item_attributes",
"arguments": {
"itemId": "ITEM_ID",
"values": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 item_not_found, item_has_no_type, attribute_not_on_type, attribute_value_invalid, attribute_value_required · 429 slow down
attributes.set_item_type
Give an item its product type — Chosen once, as in Saleor — it decides which attributes the item carries.
Needs attributes:write · changes data
https://app.getera.io/api/v1/attribute_item_options/actions/set_item_type- Canonical address:
POST /api/v1/actions/attributes.set_item_type
Input
| Field | Type | Required | Notes |
|---|---|---|---|
itemId | uuid | yes | Item · id of item |
productTypeId | uuid | yes | Product type · id of product_type |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
item_not_found | The item must exist. |
not_found | The type must exist. |
item_type_locked | An item’s type is chosen once. |
type_does_not_fit_item | Shipping and gift-card kind must agree with the item. |
Events it publishes: attributes.item_type_set, attributes.variant_axes_synced
What it writes: attribute_item_option (upsert) — Maps a picker attribute onto the catalogue axis (item.md §9b); item_product_type (insert) — Records the item’s type
curl -X POST https://app.getera.io/api/v1/attribute_item_options/actions/set_item_type \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"itemId":"ITEM_ID","productTypeId":"PRODUCT_TYPE_ID"}'{
"ok": true,
"result": "…",
"events": [
"attributes.item_type_set",
"attributes.variant_axes_synced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributeItemOptions.setItemType({
itemId: "ITEM_ID",
productTypeId: "PRODUCT_TYPE_ID"
})era attribute_item_options set_item_type \
--item-id "ITEM_ID" \
--product-type-id "PRODUCT_TYPE_ID"{
"name": "era__attributes__set_item_type",
"arguments": {
"itemId": "ITEM_ID",
"productTypeId": "PRODUCT_TYPE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 item_not_found, not_found, item_type_locked, type_does_not_fit_item · 429 slow down
attributes.set_variant_attributes
Fill in a variant’s attributes — The variant-level values — size, colour — including the ones that drive the picker.
Needs attributes:write · changes data
https://app.getera.io/api/v1/attribute_item_options/actions/set_variant_attributes- Canonical address:
POST /api/v1/actions/attributes.set_variant_attributes
Input
| Field | Type | Required | Notes |
|---|---|---|---|
itemVariantId | uuid | yes | Variant · id of item_variant |
values | object / array | yes | Values |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
variant_not_found | The variant must exist. |
item_has_no_type | The item must have a product type. |
attribute_not_on_type | Every attribute must be on the type at this level. |
attribute_value_invalid | A value must match its attribute’s type and choices. |
attribute_value_required | Required attributes must end up with a value. |
Events it publishes: attributes.variant_attributes_changed, attributes.variant_axes_synced
What it writes: attribute_item_option (upsert) — Maps a picker attribute onto the catalogue axis (item.md §9b); variant_attribute_value (upsert) — Replaces the values of each attribute sent
curl -X POST https://app.getera.io/api/v1/attribute_item_options/actions/set_variant_attributes \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"itemVariantId":"ITEM_VARIANT_ID","values":{}}'{
"ok": true,
"result": "…",
"events": [
"attributes.variant_attributes_changed",
"attributes.variant_axes_synced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributeItemOptions.setVariantAttributes({
itemVariantId: "ITEM_VARIANT_ID",
values: {}
})era attribute_item_options set_variant_attributes --data '{"itemVariantId":"ITEM_VARIANT_ID","values":{}}'{
"name": "era__attributes__set_variant_attributes",
"arguments": {
"itemVariantId": "ITEM_VARIANT_ID",
"values": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 variant_not_found, item_has_no_type, attribute_not_on_type, attribute_value_invalid, attribute_value_required · 429 slow down
attributes.unassign_attribute
Remove an attribute from a type — The values that type’s products carried for it go too.
Needs attributes:manage · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/product_type_attributes/actions/unassign_attribute- Canonical address:
POST /api/v1/actions/attributes.unassign_attribute
Input
| Field | Type | Required | Notes |
|---|---|---|---|
productTypeId | uuid | yes | Product type · id of product_type |
attributeId | uuid | yes | Attribute · id of attribute |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The type and the attribute must exist. |
attribute_not_on_type | The attribute must be on the type. |
Events it publishes: attributes.type_attributes_changed
What it writes: product_type_attribute (delete) — Removes the assignment; item_attribute_value (delete) — Removes products’ values for it; variant_attribute_value (delete) — Removes variants’ values for it
curl -X POST https://app.getera.io/api/v1/product_type_attributes/actions/unassign_attribute \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"productTypeId":"PRODUCT_TYPE_ID","attributeId":"ATTRIBUTE_ID"}'{
"ok": true,
"result": "…",
"events": [
"attributes.type_attributes_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productTypeAttributes.unassignAttribute({
productTypeId: "PRODUCT_TYPE_ID",
attributeId: "ATTRIBUTE_ID"
})era product_type_attributes unassign_attribute \
--product-type-id "PRODUCT_TYPE_ID" \
--attribute-id "ATTRIBUTE_ID"{
"name": "era__attributes__unassign_attribute",
"arguments": {
"productTypeId": "PRODUCT_TYPE_ID",
"attributeId": "ATTRIBUTE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, attribute_not_on_type · 429 slow down
attributes.update_assignment
Switch the variant picker — Whether a variant attribute appears as the storefront’s “Size: S M L” buttons.
Needs attributes:manage · changes data
https://app.getera.io/api/v1/attribute_item_options/actions/update_assignment- Canonical address:
POST /api/v1/actions/attributes.update_assignment
Input
| Field | Type | Required | Notes |
|---|---|---|---|
productTypeId | uuid | yes | Product type · id of product_type |
attributeId | uuid | yes | Attribute · id of attribute |
variantSelection | boolean | yes | Drives the variant picker |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The type and the attribute must exist. |
attribute_not_on_type | It must be a variant attribute of the type. |
variant_selection_not_allowed | Only dropdown, yes/no, swatch or number drive the picker. |
Events it publishes: attributes.type_attributes_changed, attributes.variant_axes_synced
What it writes: attribute_item_option (upsert) — Maps a picker attribute onto the catalogue axis (item.md §9b); product_type_attribute (update) — Sets variant_selection
curl -X POST https://app.getera.io/api/v1/attribute_item_options/actions/update_assignment \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"productTypeId":"PRODUCT_TYPE_ID","attributeId":"ATTRIBUTE_ID","variantSelection":true}'{
"ok": true,
"result": "…",
"events": [
"attributes.type_attributes_changed",
"attributes.variant_axes_synced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.attributeItemOptions.updateAssignment({
productTypeId: "PRODUCT_TYPE_ID",
attributeId: "ATTRIBUTE_ID",
variantSelection: true
})era attribute_item_options update_assignment \
--product-type-id "PRODUCT_TYPE_ID" \
--attribute-id "ATTRIBUTE_ID" \
--variant-selection true{
"name": "era__attributes__update_assignment",
"arguments": {
"productTypeId": "PRODUCT_TYPE_ID",
"attributeId": "ATTRIBUTE_ID",
"variantSelection": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold attributes:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, attribute_not_on_type, variant_selection_not_allowed · 429 slow down
Data models
AttributeItemOption
Table attribute_item_option · Resource attribute_item_options
GET /api/v1/attribute_item_optionsGET /api/v1/attribute_item_options/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
item_id | string (uuid) · uuid | item | ||
attribute_id | string (uuid) · uuid | attribute | ||
item_option_id | string (uuid) · uuid | item_option | ||
created_at | string (date-time) · timestamptz |
Primary key: id
Unique: item_id, attribute_id · item_option_id
AttributeValue
Table attribute_value · Resource attribute_values
GET /api/v1/attribute_valuesGET /api/v1/attribute_values/{id}PATCH /api/v1/attribute_values/{id}→attributes.save_attribute_valueDELETE /api/v1/attribute_values/{id}→attributes.delete_attribute_value
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
attribute_id | string (uuid) · uuid | attribute | ||
name | string · text | |||
slug | string · text | |||
value | string · text | |||
file_id | string (uuid) · uuid | yes | file | |
sort_order | integer · int4 | |||
external_reference | string · text | yes | ||
created_at | string (date-time) · timestamptz |
Primary key: id
Unique: attribute_id, slug
Checks:
CHECK (((value = ''::text) OR (file_id IS NULL)))
Attribute
Table attribute · Resource attributes
GET /api/v1/attributesGET /api/v1/attributes/{id}PATCH /api/v1/attributes/{id}→attributes.save_attributeDELETE /api/v1/attributes/{id}→attributes.delete_attribute
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
name | string · text | |||
slug | string · text | |||
input_type | string · text | dropdown, multiselect, file, reference, single-reference, numeric, rich-text, plain-text, swatch, boolean, date, date-time | ||
entity_type | string · text | yes | Product, ProductVariant, Category | |
unit | string · text | yes | mm, cm, dm, m, km, ft, yd, inch, sq_mm, sq_cm, sq_dm, sq_m, sq_km, sq_ft, sq_yd, sq_inch, cubic_millimeter, cubic_centimeter, cubic_decimeter, cubic_meter, liter, cubic_foot, cubic_inch, cubic_yard, qt, pint, fl_oz, acre_in, acre_ft, g, lb, oz, kg, tonne | |
value_required | boolean · bool | |||
is_variant_only | boolean · bool | |||
visible_in_storefront | boolean · bool | |||
filterable_in_storefront | boolean · bool | |||
filterable_in_dashboard | boolean · bool | |||
storefront_search_position | integer · int4 | |||
available_in_grid | boolean · bool | |||
external_reference | string · text | yes | ||
metadata | object · jsonb | |||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK (((input_type = ANY (ARRAY['reference'::text, 'single-reference'::text])) = (entity_type IS NOT NULL)))CHECK (((unit IS NULL) OR (input_type = 'numeric'::text)))
ItemAttributeValue
Table item_attribute_value · Resource product_attribute_values
GET /api/v1/product_attribute_valuesGET /api/v1/product_attribute_values/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
item_id | string (uuid) · uuid | item | ||
attribute_id | string (uuid) · uuid | attribute | ||
value_id | string (uuid) · uuid | yes | attribute_value | |
text_value | string · text | yes | ||
numeric_value | number · float8 | yes | ||
boolean_value | boolean · bool | yes | ||
date_value | string (date-time) · timestamptz | yes | ||
file_id | string (uuid) · uuid | yes | file | |
reference_item_id | string (uuid) · uuid | yes | item | |
reference_variant_id | string (uuid) · uuid | yes | item_variant | |
reference_category_id | string (uuid) · uuid | yes | item_category | |
sort_order | integer · int4 | |||
created_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((num_nonnulls(value_id, text_value, numeric_value, boolean_value, date_value, file_id, reference_item_id, reference_variant_id, reference_category_id) = 1))
ProductTypeAttribute
Table product_type_attribute · Resource product_type_attributes
GET /api/v1/product_type_attributesGET /api/v1/product_type_attributes/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
product_type_id | string (uuid) · uuid | product_type | ||
attribute_id | string (uuid) · uuid | attribute | ||
level | string · text | product, variant | ||
variant_selection | boolean · bool | |||
sort_order | integer · int4 |
Primary key: id
Unique: product_type_id, attribute_id
Checks:
CHECK (((level = 'variant'::text) OR (NOT variant_selection)))
ItemProductType
Table item_product_type · Resource product_type_links
GET /api/v1/product_type_linksGET /api/v1/product_type_links/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
item_id | string (uuid) · uuid | item | ||
product_type_id | string (uuid) · uuid | product_type | ||
created_at | string (date-time) · timestamptz |
Primary key: id
Unique: item_id
ProductType
Table product_type · Resource product_types
GET /api/v1/product_typesGET /api/v1/product_types/{id}PATCH /api/v1/product_types/{id}→attributes.save_product_typeDELETE /api/v1/product_types/{id}→attributes.delete_product_type
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
name | string · text | |||
slug | string · text | |||
kind | string · text | normal, gift_card | ||
is_shipping_required | boolean · bool | |||
weight_grams | string (int64) · int8 | |||
tax_class_id | string (uuid) · uuid | yes | ||
metadata | object · jsonb | |||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((weight_grams >= 0))
VariantAttributeValue
Table variant_attribute_value · Resource variant_attribute_values
GET /api/v1/variant_attribute_valuesGET /api/v1/variant_attribute_values/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
item_variant_id | string (uuid) · uuid | item_variant | ||
attribute_id | string (uuid) · uuid | attribute | ||
value_id | string (uuid) · uuid | yes | attribute_value | |
text_value | string · text | yes | ||
numeric_value | number · float8 | yes | ||
boolean_value | boolean · bool | yes | ||
date_value | string (date-time) · timestamptz | yes | ||
file_id | string (uuid) · uuid | yes | file | |
reference_item_id | string (uuid) · uuid | yes | item | |
reference_variant_id | string (uuid) · uuid | yes | item_variant | |
reference_category_id | string (uuid) · uuid | yes | item_category | |
sort_order | integer · int4 | |||
created_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((num_nonnulls(value_id, text_value, numeric_value, boolean_value, date_value, file_id, reference_item_id, reference_variant_id, reference_category_id) = 1))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
attributes.attribute_deleted | An attribute was removed. | id |
attributes.attribute_saved | An attribute was created or edited. | id, slug, inputType |
attributes.attribute_values_changed | An attribute’s choices changed. | attributeId |
attributes.item_attributes_changed | An item’s attribute values changed. | itemId, count |
attributes.item_type_set | An item was given its product type. | itemId, productTypeId |
attributes.product_type_deleted | A product type was removed. | id |
attributes.product_type_saved | A product type was created or edited. | id, slug |
attributes.type_attributes_changed | A type’s attribute set changed. | productTypeId, count |
attributes.variant_attributes_changed | A variant’s attribute values changed. | itemVariantId, itemId, count |
attributes.variant_axes_synced | Picker attributes were mapped onto catalogue variant axes. | count, itemId |