ReferenceReference by module

Catalog API

Every Catalog action (25) and data model (9): endpoints, scopes, input, preconditions, events and examples.

Everything you sell or book — products, services, prices, categories and brands.

Module code catalog · 25 actions · 9 data models · 19 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: catalog (everything), catalog:read (read only) or catalog:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
brandsitem_brand/api/v1/brandscatalog.save_brand
categoriesitem_category/api/v1/categoriescatalog.update_category
product_componentsitem_component/api/v1/product_components
product_filesitem_file/api/v1/product_files
product_option_valuesitem_option_value/api/v1/product_option_values
product_optionsitem_option/api/v1/product_options
productsitem/api/v1/productscatalog.create_itemcatalog.update_item
variant_optionsitem_variant_option/api/v1/variant_options
variantsitem_variant/api/v1/variantscatalog.save_variant

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

ActionNeedsWhat it does
catalog.activatecatalog:writeOffer it again
catalog.assign_imagescatalog:writeAssign image
catalog.attach_filecatalog:writeAttach a picture or file
catalog.bulk_update_itemscatalog:writeChange several items
catalog.create_categorycatalog:manageAdd a category
catalog.create_itemcatalog:writeAdd to the catalogue
catalog.deactivatecatalog:deleteStop offering it
catalog.delete_itemscatalog:deleteDelete items
catalog.detach_filecatalog:writeRemove an attachment
catalog.email_product_exportcatalog:exportEmail product export
catalog.move_categorycatalog:manageMove a category
catalog.publishcatalog:writePublish
catalog.remove_variantcatalog:deleteRemove a variant
catalog.reorder_filescatalog:writeReorder pictures
catalog.save_brandcatalog:manageAdd or edit a brand
catalog.save_variantcatalog:writeAdd or edit a variant
catalog.set_brand_visibilitycatalog:manageShow or hide a brand
catalog.set_optionscatalog:writeSet the variant axes
catalog.set_package_contentscatalog:writeSet what a package contains
catalog.set_pricecatalog:writeSet a price
catalog.unpublishcatalog:writeUnpublish
catalog.update_categorycatalog:manageEdit a category
catalog.update_filecatalog:writeDescribe a picture
catalog.update_itemcatalog:writeEdit a catalogue entry
catalog.update_item_metadatacatalog:writeEdit product metadata

catalog.activate

Offer it again — Puts it back on the list.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/products/{id}/actions/activate
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.activate

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Item · id of item

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe entry must exist.
already_activeIt must be off the list.

Events it publishes: catalog.activated

What it writes: item (update) — Marks it active

curl -X POST https://app.getera.io/api/v1/products/PRODUCT_ID/actions/activate \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.activated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.activate("PRODUCT_ID")
era products activate PRODUCT_ID
tools/call
{
  "name": "era__catalog__activate",
  "arguments": {
    "id": "PRODUCT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_active · 429 slow down

catalog.assign_images

Assign image — Appends the chosen image to every selected service or product, preserving their galleries.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/product_files/actions/assign_images
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.assign_images

Input

FieldTypeRequiredNotes
idsobject / arrayyesSelected records
fileIduuidyesImage · id of file

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_itemsChoose one to 500 records.
file_not_foundThe file must be accessible in this workspace.
image_requiredThe file must be an image.
not_foundEvery selected record must be accessible.

Events it publishes: catalog.files_changed

What it writes: item_file (insert) — Appends an image, keeping the existing gallery

curl -X POST https://app.getera.io/api/v1/product_files/actions/assign_images \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"ids":{},"fileId":"FILE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.files_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productFiles.assignImages({
  ids: {},
  fileId: "FILE_ID"
})
era product_files assign_images --data '{"ids":{},"fileId":"FILE_ID"}'
tools/call
{
  "name": "era__catalog__assign_images",
  "arguments": {
    "ids": {},
    "fileId": "FILE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_items, file_not_found, image_required, not_found · 429 slow down

catalog.attach_file

Attach a picture or file — A photo, a video, a datasheet or a download — on the item or on one variant.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/product_files/actions/attach_file
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.attach_file

Input

FieldTypeRequiredNotes
itemIduuidyesItem · id of item
fileIduuidFile · id of file
externalUrlstringExternal URL · max 1000
variantIduuidVariant · id of item_variant
rolestring (enum)Role · Allowed values: image, video, datasheet, download
altstringAlt text · max 250
sortOrdernumberOrder

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe item must exist.
nothing_to_attachA file or an external URL is needed.
file_not_foundThe file must exist.
variant_not_on_itemThe variant must belong to this item.

Events it publishes: catalog.files_changed

What it writes: item_file (insert) — Attaches the file

curl -X POST https://app.getera.io/api/v1/product_files/actions/attach_file \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"itemId":"ITEM_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.files_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productFiles.attachFile({
  itemId: "ITEM_ID"
})
era product_files attach_file \
  --item-id "ITEM_ID"
tools/call
{
  "name": "era__catalog__attach_file",
  "arguments": {
    "itemId": "ITEM_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, nothing_to_attach, file_not_found, variant_not_on_item · 429 slow down

catalog.bulk_update_items

Change several items — Publish, unpublish, put back on or take off the list, or move to a category — from the list.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/products/actions/bulk_update_items
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.bulk_update_items

Input

FieldTypeRequiredNotes
itemIdsobject / arrayyesItems
operationstring (enum)yesOperation · Allowed values: publish, unpublish, activate, deactivate, set_category
categoryIduuidCategory · id of item_category

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_itemsAt least one item is chosen.
not_foundEvery item must exist.
category_not_foundThe category must exist.

Events it publishes: catalog.published, catalog.unpublished, catalog.activated, catalog.deactivated, catalog.item_updated

What it writes: item (update) — Writes the change on each item it applies to

curl -X POST https://app.getera.io/api/v1/products/actions/bulk_update_items \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"itemIds":{},"operation":"publish"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.published",
    "catalog.unpublished",
    "catalog.activated",
    "catalog.deactivated",
    "catalog.item_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.bulkUpdateItems({
  itemIds: {},
  operation: "publish"
})
era products bulk_update_items --data '{"itemIds":{},"operation":"publish"}'
tools/call
{
  "name": "era__catalog__bulk_update_items",
  "arguments": {
    "itemIds": {},
    "operation": "publish"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_items, not_found, category_not_found · 429 slow down

catalog.create_category

Add a category — Groups the catalogue, on this screen and on the storefront.

Needs catalog:manage · changes data

POSThttps://app.getera.io/api/v1/categories/{id}/actions/create_category
API key or OAuth token · catalog:manage
  • Canonical address: POST /api/v1/actions/catalog.create_category

Input

FieldTypeRequiredNotes
namestringyesName · max 200
slugstringAddress · max 200
descriptionstringDescription · max 2000
contentobject / arrayPage content
iconstringIcon · max 64
imageFileIduuidImage · id of file
parentIduuidFilled from the path {id} · Inside · id of item_category
isActivebooleanVisible
sortOrdernumberOrder
seoTitlestringSearch title · max 200
seoDescriptionstringSearch description · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_nameThe name must produce a usable address.
slug_takenThe address must be free in this workspace.
parent_not_foundThe parent category must exist.

Events it publishes: catalog.category_created

What it writes: item_category (insert) — Creates the category

curl -X POST https://app.getera.io/api/v1/categories/CATEGORY_ID/actions/create_category \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.category_created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.categories.createCategory("CATEGORY_ID", {
  name: "Name"
})
era categories create_category CATEGORY_ID \
  --name "Name"
tools/call
{
  "name": "era__catalog__create_category",
  "arguments": {
    "parentId": "CATEGORY_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_name, slug_taken, parent_not_found · 429 slow down

catalog.create_item

Add to the catalogue — Puts something new on the list of what can be sold or booked, with its first variant.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/products/actions/create_item
API key or OAuth token · catalog:write
  • REST alias: POST /api/v1/products
  • Canonical address: POST /api/v1/actions/catalog.create_item

Input

FieldTypeRequiredNotes
namestringyesName · max 200
slugstringAddress · max 200
kindstring (enum)Kind · A service has time; a product ships. A package is several of either. · Allowed values: service, product, package, gift_card
fulfilmentstring (enum)Delivered as · Allowed values: appointment, home_service, group_class, shipping, digital
categoryIduuidCategory · id of item_category
brandIduuidBrand · id of item_brand
descriptionstringDescription · max 4000
summarystringShort description · max 500
contentobject / arrayPage content
sortOrdernumberOrder
modelstringModel · max 200
skustringSKU · max 100
priceAmountnumber (money)Price (minor units) · Leave empty for “ask us” — 55.4% of the live catalogue is priced that way. · min 0
durationMinutesnumberDuration (minutes) · min 5 · max 1440
capacitynumberCapacity · Above 1 makes it a class. · min 1
inShopbooleanPut it in the shop · Published at once and priced in the shop currency. No shop → store_missing; then retail.set_item_listing (no channel) lists it.
imageFileIduuidPicture · An uploaded file — e.g. the photo attached in chat. · id of file

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_nameThe name must produce a usable address.
store_missingPut in the shop, the workspace must have an open shop (retail.open_shop).
file_not_foundThe picture must be a file in this workspace.
slug_takenThe address must be free in this workspace.
fulfilment_not_allowed_for_kindA product does not take appointments.
duration_on_productA product has no duration.
category_not_foundThe category must exist.
brand_not_foundThe brand must exist.

Events it publishes: catalog.item_created, catalog.files_changed, catalog.published

What it writes: item (insert) — Creates the catalogue entry; item_variant (insert) — Creates its default variant; item_file (insert) — Attaches the picture, when one is given

curl -X POST https://app.getera.io/api/v1/products/actions/create_item \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.item_created",
    "catalog.files_changed",
    "catalog.published"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.create({
  name: "Name"
})
era products create \
  --name "Name"
tools/call
{
  "name": "era__catalog__create_item",
  "arguments": {
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_name, store_missing, file_not_found, slug_taken, fulfilment_not_allowed_for_kind, duration_on_product, category_not_found, brand_not_found · 429 slow down

catalog.deactivate

Stop offering it — Takes it off the list, naming any appointments already booked for it.

Needs catalog:delete · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/products/{id}/actions/deactivate
API key or OAuth token · catalog:delete
  • Canonical address: POST /api/v1/actions/catalog.deactivate

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Item · id of item
forcebooleanDeactivate anyway

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe entry must exist.
already_inactiveIt must still be on the list.
has_upcomingBooked appointments are named before it is taken off.

Events it publishes: catalog.deactivated

What it writes: item (update) — Marks it inactive

curl -X POST https://app.getera.io/api/v1/products/PRODUCT_ID/actions/deactivate \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"force":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.deactivated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.deactivate("PRODUCT_ID", {
  force: true
})
era products deactivate PRODUCT_ID \
  --force true
tools/call
{
  "name": "era__catalog__deactivate",
  "arguments": {
    "id": "PRODUCT_ID",
    "force": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_inactive, has_upcoming · 429 slow down

catalog.delete_items

Delete items — Removes them for good. Orders keep their frozen names; bookings and packages refuse it.

Needs catalog:delete · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/products/actions/delete_items
API key or OAuth token · catalog:delete
  • Canonical address: POST /api/v1/actions/catalog.delete_items

Input

FieldTypeRequiredNotes
itemIdsobject / arrayyesItems

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_itemsAt least one item is chosen.
not_foundEvery item must exist.
item_in_useNothing a booking, package or class points at is deleted.

Events it publishes: catalog.item_deleted

What it writes: item (delete) — Removes the items and their variants

curl -X POST https://app.getera.io/api/v1/products/actions/delete_items \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"itemIds":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.item_deleted"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.deleteItems({
  itemIds: {}
})
era products delete_items --data '{"itemIds":{}}'
tools/call
{
  "name": "era__catalog__delete_items",
  "arguments": {
    "itemIds": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_items, not_found, item_in_use · 429 slow down

catalog.detach_file

Remove an attachment — Queues it for removal, as the source does.

Needs catalog:write · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/product_files/{id}/actions/detach_file
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.detach_file

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Attachment · id of item_file

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
attachment_not_foundThe attachment must exist and still be attached.

Events it publishes: catalog.files_changed

What it writes: item_file (update) — Marks it for removal

curl -X POST https://app.getera.io/api/v1/product_files/PRODUCT_FILE_ID/actions/detach_file \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.files_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productFiles.detachFile("PRODUCT_FILE_ID")
era product_files detach_file PRODUCT_FILE_ID
tools/call
{
  "name": "era__catalog__detach_file",
  "arguments": {
    "id": "PRODUCT_FILE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 attachment_not_found · 429 slow down

catalog.email_product_export

Email product export — Builds the selected XLSX in the job worker and emails it as an attachment.

Needs catalog:export · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/actions/catalog.email_product_export
API key or OAuth token · catalog:export

Input

FieldTypeRequiredNotes
emailstringyesRecipient · max 320
fieldsobject / arrayyesFields
filterobject / arrayCurrent filters

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_export_emailThe recipient must be a usable email address.
invalid_export_fieldsAt least one known product export field must be selected.

Events it publishes: catalog.export_queued

curl -X POST https://app.getera.io/api/v1/actions/catalog.email_product_export \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"email":"Recipient","fields":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.export_queued"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("catalog.email_product_export", {
  email: "Recipient",
  fields: {}
})
era catalog email_product_export --data '{"email":"Recipient","fields":{}}'
tools/call
{
  "name": "era__catalog__email_product_export",
  "arguments": {
    "email": "Recipient",
    "fields": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:export · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_export_email, invalid_export_fields · 429 slow down

catalog.move_category

Move a category — Puts a category inside another one, or back at the root.

Needs catalog:manage · changes data

POSThttps://app.getera.io/api/v1/categories/{id}/actions/move_category
API key or OAuth token · catalog:manage
  • Canonical address: POST /api/v1/actions/catalog.move_category

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Category · id of item_category
parentIduuidInside · id of item_category

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe category must exist.
parent_not_foundThe parent category must exist.
cycleA category cannot be moved inside itself.

Events it publishes: catalog.category_moved

What it writes: item_category (update) — Moves the category

curl -X POST https://app.getera.io/api/v1/categories/CATEGORY_ID/actions/move_category \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"parentId":"ITEM_CATEGORY_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.category_moved"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.categories.moveCategory("CATEGORY_ID", {
  parentId: "ITEM_CATEGORY_ID"
})
era categories move_category CATEGORY_ID \
  --parent-id "ITEM_CATEGORY_ID"
tools/call
{
  "name": "era__catalog__move_category",
  "arguments": {
    "id": "CATEGORY_ID",
    "parentId": "ITEM_CATEGORY_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, parent_not_found, cycle · 429 slow down

catalog.publish

Publish — Puts it on the storefront.

Needs catalog:write · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/products/{id}/actions/publish
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.publish

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Item · id of item

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe entry must exist.
already_publishedIt must still be a draft.
no_variantThere must be something to sell.
no_priceA gift card needs a value.

Events it publishes: catalog.published

What it writes: item (update) — Stamps published_at

curl -X POST https://app.getera.io/api/v1/products/PRODUCT_ID/actions/publish \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.published"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.publish("PRODUCT_ID")
era products publish PRODUCT_ID
tools/call
{
  "name": "era__catalog__publish",
  "arguments": {
    "id": "PRODUCT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_published, no_variant, no_price · 429 slow down

catalog.remove_variant

Remove a variant — Drops one variant, unless it is the last or something already points at it.

Needs catalog:delete · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/variants/{id}/actions/remove_variant
API key or OAuth token · catalog:delete
  • Canonical address: POST /api/v1/actions/catalog.remove_variant

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Variant · id of item_variant

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
variant_not_foundThe variant must exist.
last_variantAn item always keeps at least one variant.
variant_in_useBookings and packages pointing at it are named first.

Events it publishes: catalog.variant_removed

What it writes: item_variant (delete) — Removes the variant

curl -X POST https://app.getera.io/api/v1/variants/VARIANT_ID/actions/remove_variant \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.variant_removed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.variants.removeVariant("VARIANT_ID")
era variants remove_variant VARIANT_ID
tools/call
{
  "name": "era__catalog__remove_variant",
  "arguments": {
    "id": "VARIANT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 variant_not_found, last_variant, variant_in_use · 429 slow down

catalog.reorder_files

Reorder pictures — The order the storefront shows them in.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/product_files/actions/reorder_files
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.reorder_files

Input

FieldTypeRequiredNotes
itemIduuidyesItem · id of item
attachmentIdsobject / arrayyesAttachments

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe item must exist.
attachment_not_on_itemEvery attachment must be this item’s.

Events it publishes: catalog.files_changed

What it writes: item_file (update) — Writes the order

curl -X POST https://app.getera.io/api/v1/product_files/actions/reorder_files \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"itemId":"ITEM_ID","attachmentIds":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.files_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productFiles.reorderFiles({
  itemId: "ITEM_ID",
  attachmentIds: {}
})
era product_files reorder_files --data '{"itemId":"ITEM_ID","attachmentIds":{}}'
tools/call
{
  "name": "era__catalog__reorder_files",
  "arguments": {
    "itemId": "ITEM_ID",
    "attachmentIds": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, attachment_not_on_item · 429 slow down

catalog.save_brand

Add or edit a brand — Whose product this is — with its own page, site and logo.

Needs catalog:manage · changes data

POSThttps://app.getera.io/api/v1/brands/{id}/actions/save_brand
API key or OAuth token · catalog:manage
  • REST alias: PATCH /api/v1/brands/{id}
  • Canonical address: POST /api/v1/actions/catalog.save_brand

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Brand · id of item_brand
namestringyesName · max 200
slugstringAddress · max 200
descriptionstringDescription · max 2000
websiteUrlstringWebsite · max 500
parentIduuidPart of · id of item_brand
isFeaturedbooleanShow in the showcase
isActivebooleanVisible
sortOrdernumberOrder
imageFileIduuidLogo · id of file

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_nameThe name must produce a usable address.
slug_takenThe address must be free in this workspace.
parent_not_foundThe parent brand must exist.
cycleA brand cannot be its own parent.
not_foundThe brand must exist when editing.

Events it publishes: catalog.brand_created, catalog.brand_updated

What it writes: item_brand (upsert) — Writes the brand

curl -X POST https://app.getera.io/api/v1/brands/BRAND_ID/actions/save_brand \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.brand_created",
    "catalog.brand_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.brands.saveBrand("BRAND_ID", {
  name: "Name"
})
era brands save_brand BRAND_ID \
  --name "Name"
tools/call
{
  "name": "era__catalog__save_brand",
  "arguments": {
    "id": "BRAND_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_name, slug_taken, parent_not_found, cycle, not_found · 429 slow down

catalog.save_variant

Add or edit a variant — The thing actually sold: its SKU, price, duration, capacity and axes.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/variants/{id}/actions/save_variant
API key or OAuth token · catalog:write
  • REST alias: PATCH /api/v1/variants/{id}
  • Canonical address: POST /api/v1/actions/catalog.save_variant

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Variant · id of item_variant
itemIduuidItem · id of item
namestringVariant name · max 200
skustringSKU · max 100
priceAmountnumber (money)Price (minor units) · min 0
costPriceAmountnumber (money)Cost (minor units) · min 0
durationMinutesnumberDuration (minutes) · min 5 · max 1440
capacitynumberCapacity · min 1
weightGramsnumberWeight (g) · min 0
quantityLimitPerCustomernumberLimit per customer · min 1
trackInventorybooleanTrack stock
sortOrdernumberOrder
optionValueIdsstringOption values · Comma-separated option value ids — one per axis. · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe item must exist.
variant_not_foundThe variant must exist.
sku_takenA SKU belongs to one variant.
weight_on_unshippedOnly something that ships has a weight.
duration_on_productA product has no duration.
option_value_not_on_itemEvery axis value must belong to this item.
variant_combination_existsTwo variants may not share a combination.

Events it publishes: catalog.variant_changed

What it writes: item_variant (upsert) — Writes the variant; item_variant_option (upsert) — Writes which axes it sits on

curl -X POST https://app.getera.io/api/v1/variants/VARIANT_ID/actions/save_variant \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"itemId":"ITEM_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.variant_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.variants.saveVariant("VARIANT_ID", {
  itemId: "ITEM_ID"
})
era variants save_variant VARIANT_ID \
  --item-id "ITEM_ID"
tools/call
{
  "name": "era__catalog__save_variant",
  "arguments": {
    "id": "VARIANT_ID",
    "itemId": "ITEM_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, variant_not_found, sku_taken, weight_on_unshipped, duration_on_product, option_value_not_on_item, variant_combination_exists · 429 slow down

catalog.set_brand_visibility

Show or hide a brand — Keeps product history intact while controlling whether the brand appears in pickers and showcases.

Needs catalog:manage · changes data

POSThttps://app.getera.io/api/v1/brands/{id}/actions/set_brand_visibility
API key or OAuth token · catalog:manage
  • Canonical address: POST /api/v1/actions/catalog.set_brand_visibility

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Brand · id of item_brand
isActivebooleanyesVisible

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe brand must exist.

Events it publishes: catalog.brand_updated

What it writes: item_brand (update) — Changes storefront visibility

curl -X POST https://app.getera.io/api/v1/brands/BRAND_ID/actions/set_brand_visibility \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"isActive":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.brand_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.brands.setBrandVisibility("BRAND_ID", {
  isActive: true
})
era brands set_brand_visibility BRAND_ID \
  --is-active true
tools/call
{
  "name": "era__catalog__set_brand_visibility",
  "arguments": {
    "id": "BRAND_ID",
    "isActive": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

catalog.set_options

Set the variant axes — What tells one variant from another — size, colour, length.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/product_options/actions/set_options
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.set_options

Input

FieldTypeRequiredNotes
itemIduuidyesItem · id of item
optionsobject / arrayyesOptions · [{ "name": "Size", "values": [{ "label": "S" }, { "label": "M" }] }]

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe item must exist.
invalid_optionsEvery option needs a name and labelled values.
option_in_use_by_variantAn axis a variant sits on cannot vanish.

Events it publishes: catalog.options_changed

What it writes: item_option (upsert) — Replaces the axes; item_option_value (upsert) — Replaces their values

curl -X POST https://app.getera.io/api/v1/product_options/actions/set_options \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"itemId":"ITEM_ID","options":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.options_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productOptions.setOptions({
  itemId: "ITEM_ID",
  options: {}
})
era product_options set_options --data '{"itemId":"ITEM_ID","options":{}}'
tools/call
{
  "name": "era__catalog__set_options",
  "arguments": {
    "itemId": "ITEM_ID",
    "options": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, invalid_options, option_in_use_by_variant · 429 slow down

catalog.set_package_contents

Set what a package contains — Replaces a package’s contents — variants, with quantities, in order.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/product_components/actions/set_package_contents
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.set_package_contents

Input

FieldTypeRequiredNotes
packageItemIduuidyesPackage · id of item
contentsstringyesContents · Comma-separated variant ids, optionally id:quantity — e.g. “…:10” for ten sessions. · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe package must exist.
not_a_packageThe entry must be of kind “package”.
package_emptyA package needs at least one line.
invalid_quantityA quantity is a whole number, one or more.
component_not_foundEvery line must point at a real variant.
self_componentA package cannot contain itself.
component_is_packagePackages do not nest.

Events it publishes: catalog.package_changed

What it writes: item_component (upsert) — Replaces the package contents

curl -X POST https://app.getera.io/api/v1/product_components/actions/set_package_contents \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"packageItemId":"ITEM_ID","contents":"Contents"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.package_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productComponents.setPackageContents({
  packageItemId: "ITEM_ID",
  contents: "Contents"
})
era product_components set_package_contents \
  --package-item-id "ITEM_ID" \
  --contents "Contents"
tools/call
{
  "name": "era__catalog__set_package_contents",
  "arguments": {
    "packageItemId": "ITEM_ID",
    "contents": "Contents"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_a_package, package_empty, invalid_quantity, component_not_found, self_component, component_is_package · 429 slow down

catalog.set_price

Set a price — The list price of one variant. Signed agreements do not move with it.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/variants/{id}/actions/set_price
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.set_price

Input

FieldTypeRequiredNotes
variantIduuidyesFilled from the path {id} · Variant · id of item_variant
priceAmountnumber (money)Price (minor units) · min 0
currencystringCurrency · max 3
costPriceAmountnumber (money)Cost (minor units) · min 0
priorPriceAmountnumber (money)Previous price · EU Omnibus. · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
variant_not_foundThe variant must exist.
currency_mismatchOne catalogue, one currency.

Events it publishes: catalog.price_changed

What it writes: item_variant (update) — Writes the price

curl -X POST https://app.getera.io/api/v1/variants/VARIANT_ID/actions/set_price \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"priceAmount":1}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.price_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.variants.setPrice("VARIANT_ID", {
  priceAmount: 1
})
era variants set_price VARIANT_ID \
  --price-amount 1
tools/call
{
  "name": "era__catalog__set_price",
  "arguments": {
    "variantId": "VARIANT_ID",
    "priceAmount": 1
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 variant_not_found, currency_mismatch · 429 slow down

catalog.unpublish

Unpublish — Takes it back to draft without taking it off the internal list.

Needs catalog:write · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/products/{id}/actions/unpublish
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.unpublish

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Item · id of item

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe entry must exist.
not_publishedIt must be published.

Events it publishes: catalog.unpublished

What it writes: item (update) — Clears published_at

curl -X POST https://app.getera.io/api/v1/products/PRODUCT_ID/actions/unpublish \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.unpublished"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.unpublish("PRODUCT_ID")
era products unpublish PRODUCT_ID
tools/call
{
  "name": "era__catalog__unpublish",
  "arguments": {
    "id": "PRODUCT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_published · 429 slow down

catalog.update_category

Edit a category — Updates the category copy, address, content and storefront visibility.

Needs catalog:manage · changes data

POSThttps://app.getera.io/api/v1/categories/{id}/actions/update_category
API key or OAuth token · catalog:manage
  • REST alias: PATCH /api/v1/categories/{id}
  • Canonical address: POST /api/v1/actions/catalog.update_category

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Category · id of item_category
namestringName · max 200
slugstringAddress · max 200
descriptionstringDescription · max 2000
contentobject / arrayPage content
iconstringIcon · max 64
imageFileIduuidImage · id of file
isActivebooleanVisible
sortOrdernumberOrder
seoTitlestringSearch title · max 200
seoDescriptionstringSearch description · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe category must exist.
invalid_nameThe name must produce a usable address.
slug_takenThe address must be free in this workspace.

Events it publishes: catalog.category_updated

What it writes: item_category (update) — Updates the category

curl -X POST https://app.getera.io/api/v1/categories/CATEGORY_ID/actions/update_category \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.category_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.categories.updateCategory("CATEGORY_ID", {
  name: "Name"
})
era categories update_category CATEGORY_ID \
  --name "Name"
tools/call
{
  "name": "era__catalog__update_category",
  "arguments": {
    "id": "CATEGORY_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, invalid_name, slug_taken · 429 slow down

catalog.update_file

Describe a picture — Its alt text, and which variant it shows.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/product_files/{id}/actions/update_file
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.update_file

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Attachment · id of item_file
altstringAlt text · max 250
variantIduuidVariant · id of item_variant

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
attachment_not_foundThe attachment must exist and still be attached.
variant_not_on_itemThe variant must belong to the same item.

Events it publishes: catalog.files_changed

What it writes: item_file (update) — Writes the alt text and variant

curl -X POST https://app.getera.io/api/v1/product_files/PRODUCT_FILE_ID/actions/update_file \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"alt":"Alt text"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.files_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.productFiles.updateFile("PRODUCT_FILE_ID", {
  alt: "Alt text"
})
era product_files update_file PRODUCT_FILE_ID \
  --alt "Alt text"
tools/call
{
  "name": "era__catalog__update_file",
  "arguments": {
    "id": "PRODUCT_FILE_ID",
    "alt": "Alt text"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 attachment_not_found, variant_not_on_item · 429 slow down

catalog.update_item

Edit a catalogue entry — Its name, address, grouping, copy and SEO.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/products/{id}/actions/update_item
API key or OAuth token · catalog:write
  • REST alias: PATCH /api/v1/products/{id}
  • Canonical address: POST /api/v1/actions/catalog.update_item

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Item · id of item
namestringName · max 200
slugstringAddress · max 200
categoryIduuidCategory · id of item_category
brandIduuidBrand · id of item_brand
descriptionstringDescription · max 4000
summarystringShort description · max 500
contentobject / arrayPage content
modelstringModel · max 200
seoTitlestringSEO title · max 200
seoDescriptionstringSEO description · max 500
isFeaturedbooleanFeatured
fulfilmentstring (enum)Delivered as · Allowed values: appointment, home_service, group_class, shipping, digital
externalReferencestringExternal key · max 200
sortOrdernumberOrder
featuresobject / arrayFeatures · A list of short lines shown as bullet points.

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe entry must exist.
invalid_nameThe name must produce a usable address.
slug_takenThe address must be free in this workspace.
fulfilment_not_allowed_for_kindA product does not take appointments.
category_not_foundThe category must exist.
brand_not_foundThe brand must exist.

Events it publishes: catalog.item_updated

What it writes: item (update) — Updates the catalogue entry

curl -X POST https://app.getera.io/api/v1/products/PRODUCT_ID/actions/update_item \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.item_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.updateItem("PRODUCT_ID", {
  name: "Name"
})
era products update_item PRODUCT_ID \
  --name "Name"
tools/call
{
  "name": "era__catalog__update_item",
  "arguments": {
    "id": "PRODUCT_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, invalid_name, slug_taken, fulfilment_not_allowed_for_kind, category_not_found, brand_not_found · 429 slow down

catalog.update_item_metadata

Edit product metadata — Replaces the entry’s key → value metadata, public or private.

Needs catalog:write · changes data

POSThttps://app.getera.io/api/v1/products/{id}/actions/update_item_metadata
API key or OAuth token · catalog:write
  • Canonical address: POST /api/v1/actions/catalog.update_item_metadata

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Item · id of item
metadataobject / arrayMetadata
privateMetadataobject / arrayPrivate metadata

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe entry must exist.

Events it publishes: catalog.item_updated

What it writes: item (update) — Writes the metadata

curl -X POST https://app.getera.io/api/v1/products/PRODUCT_ID/actions/update_item_metadata \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"metadata":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "catalog.item_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.products.updateItemMetadata("PRODUCT_ID", {
  metadata: {}
})
era products update_item_metadata PRODUCT_ID --data '{"metadata":{}}'
tools/call
{
  "name": "era__catalog__update_item_metadata",
  "arguments": {
    "id": "PRODUCT_ID",
    "metadata": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold catalog:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

Data models

ItemBrand

Table item_brand · Resource brands

  • GET /api/v1/brands
  • GET /api/v1/brands/{id}
  • PATCH /api/v1/brands/{id} → catalog.save_brand
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
slugstring · text
descriptionstring · textyes
website_urlstring · textyes
image_file_idstring (uuid) · uuidyesfile
parent_idstring (uuid) · uuidyesitem_brand
is_activeboolean · bool
is_featuredboolean · bool
sort_orderinteger · int4
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Unique: workspace_id, slug

ItemCategory

Table item_category · Resource categories

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
slugstring · text
descriptionstring · textyes
iconstring · textyes
image_file_idstring (uuid) · uuidyesfile
contentobject · jsonbyes
sort_orderinteger · int4
is_activeboolean · bool
parent_idstring (uuid) · uuidyesitem_category
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
seo_titlestring · textyes
seo_descriptionstring · textyes

Primary key: id

Unique: workspace_id, slug

ItemComponent

Table item_component · Resource product_components

  • GET /api/v1/product_components
  • GET /api/v1/product_components/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
parent_item_idstring (uuid) · uuiditem
sort_orderinteger · int4
item_variant_idstring (uuid) · uuiditem_variant
quantityinteger · int4

Primary key: id

Unique: parent_item_id, item_variant_id

Checks:

  • CHECK ((quantity >= 1))

ItemFile

Table item_file · Resource product_files

  • GET /api/v1/product_files
  • GET /api/v1/product_files/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
item_idstring (uuid) · uuiditem
variant_idstring (uuid) · uuidyesitem_variant
file_idstring (uuid) · uuidyesfile
rolestring · textimage, video, datasheet, download
altstring · textyes
external_urlstring · textyes
to_removeboolean · bool
sort_orderinteger · int4
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((file_id IS NOT NULL) OR (external_url IS NOT NULL)))

ItemOptionValue

Table item_option_value · Resource product_option_values

  • GET /api/v1/product_option_values
  • GET /api/v1/product_option_values/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
option_idstring (uuid) · uuiditem_option
labelstring · text
swatchstring · textyes
file_idstring (uuid) · uuidyesfile
sort_orderinteger · int4

Primary key: id

Unique: option_id, label

ItemOption

Table item_option · Resource product_options

  • GET /api/v1/product_options
  • GET /api/v1/product_options/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
item_idstring (uuid) · uuiditem
namestring · text
sort_orderinteger · int4

Primary key: id

Unique: item_id, name

Item

Table item · Resource products

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
category_idstring (uuid) · uuidyesitem_category
namestring · text
slugstring · text
descriptionstring · textyes
featuresstring[] · text[]yes
fulfilmentstring · textappointment, home_service, group_class, shipping, digital
is_activeboolean · bool
sort_orderinteger · int4
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
kindstring · textproduct, service, package, gift_card
summarystring · textyes
contentobject · jsonbyes
seo_titlestring · textyes
seo_descriptionstring · textyes
published_atstring (date-time) · timestamptzyes
is_featuredboolean · bool
modelstring · textyes
ratingnumber · float4yes
tax_class_idstring (uuid) · uuidyes
external_referencestring · textyes
brand_idstring (uuid) · uuidyesitem_brand
default_variant_idstring (uuid) · uuidyesitem_variant
is_publicboolean · boolyes
metadataobject · jsonb
private_metadataobject · jsonb

Primary key: id

Unique: workspace_id, slug

Checks:

  • CHECK ( CASE kind WHEN 'service'::text THEN (fulfilment = ANY (ARRAY['appointment'::text, 'home_service'::text, 'group_class'::text])) WHEN 'product'::text THEN (fulfilment = ANY (ARRAY['shipping'::text, 'digital'::text])) ELSE true END)

ItemVariantOption

Table item_variant_option · Resource variant_options

  • GET /api/v1/variant_options
  • GET /api/v1/variant_options/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
variant_idstring (uuid) · uuiditem_variant
option_idstring (uuid) · uuiditem_option
option_value_idstring (uuid) · uuiditem_option_value

Primary key: id

Unique: variant_id, option_id

ItemVariant

Table item_variant · Resource variants

  • GET /api/v1/variants
  • GET /api/v1/variants/{id}
  • PATCH /api/v1/variants/{id} → catalog.save_variant
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
item_idstring (uuid) · uuiditem
namestring · text
skustring · textyes
price_amountstring (int64) · int8yes
price_currencystring · bpcharyesmax 3
cost_price_amountstring (int64) · int8yes
prior_price_amountstring (int64) · int8yes
duration_minutesinteger · int4yes
capacityinteger · int4
weight_gramsstring (int64) · int8yes
quantity_limit_per_customerinteger · int4yes
track_inventoryboolean · bool
external_referencestring · textyes
is_activeboolean · bool
sort_orderinteger · int4
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((capacity >= 1))
  • CHECK (((cost_price_amount IS NULL) OR (cost_price_amount >= 0)))
  • CHECK (((duration_minutes IS NULL) OR ((duration_minutes >= 5) AND (duration_minutes <= 1440))))
  • CHECK (((price_amount IS NULL) OR (price_amount >= 0)))
  • CHECK (((quantity_limit_per_customer IS NULL) OR (quantity_limit_per_customer >= 1)))
  • CHECK (((weight_grams IS NULL) OR (weight_grams >= 0)))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
catalog.activatedSomething is on sale again.id
catalog.brand_createdA brand was added.id, slug
catalog.brand_updatedA brand was edited.id, slug
catalog.category_createdA category was added.id, name, slug
catalog.category_movedA category changed parent.id, parentId
catalog.category_updatedA category was edited.id, slug
catalog.deactivatedSomething stopped being sold.id, keptAppointments
catalog.export_queuedA product XLSX was queued for email delivery.jobId, email, fields
catalog.files_changedA picture or file was attached or removed.id, attachmentId
catalog.item_createdSomething was added to the catalogue.id, name, slug, kind, variantId
catalog.item_deletedA catalogue entry was deleted.id
catalog.item_updatedA catalogue entry was edited.id, slug
catalog.options_changedThe variant axes changed.id, count
catalog.package_changedA package’s contents were replaced.id, count
catalog.price_changedA list price changed.variantId, itemId, amount, currency
catalog.publishedSomething went on the storefront.id, slug
catalog.unpublishedSomething went back to draft.id
catalog.variant_changedA variant was added or edited.id, itemId
catalog.variant_removedA variant was removed.id, itemId
Was this page helpful?