Appearance
Surfaces
Interactive API Explorer: Explorer
Base: https://api.g3nretailstack.com/pvm (custom domain; stage trimmed). All routes require orgcode and either a human USM session or a service-account API key. Canonical auth: headers x-session-guid or x-api-key on all routes; body session_guid / api_key are accepted for compatibility. Query-string bearer tokens are not accepted. Owners full access; members role-gated (Product Model Administrator for non-supplier mutations, Vendor Contract Administrator for supplier mutations, Product and Vendor Viewer for reads/comments). Human-session roles are resolved via OFM member/resolve using the session + orgcode; service-account roles come from POST /usm/api_key/validate. Pagination default 8, clamp 1–256; next_token opaque JSON. Stateful record responses include revision. Mutations that update an existing revisioned record must include expected_revision; missing → expected-revision-required (HTTP 428), mismatch → conflict (HTTP 409) with the current record snapshot + current revision. Codes immutable ^[A-Z][A-Z0-9_-]{0,9}$; aliases up to 16 {tag,value} unique per org. Create endpoints that accept code may omit it and use code_pattern? + code_max_attempts?; on exhaustion: code-generation-exhausted (HTTP 409). Edits only while inactive; doom is terminal. Styles require a valid, non-doomed taxonomy category_id. Template econ currency/FX: each org has an immutable org_currency; template_econ.currency is required when monetary fields are set; if currency != org_currency then template_econ.fx is required (and must convert to org_currency).
Surface Types (explicit)
API Gateway
- Status: Available.
- Base:
https://api.g3nretailstack.com/pvm - Notes: Primary tenant surface for PVM operations.
Direct Lambda
- Status: Not offered.
- Notes: No direct Lambda surface is documented for PVM.
CLI
- Status: Available.
- Command:
g3n pvm ...(API Gateway). - Notes: See
cli/README.md.
MCP
- Status: Available.
- Canonical protocol:
https://mcp.g3nretailstack.com/pvm/PROTOCOL.md - Mirror:
https://doc.g3nretailstack.com/pvm/PROTOCOL.md
Auth + tenancy
- Required headers:
x-orgcodeandx-session-guid(user session) orx-api-key(service account). Header auth is canonical; body auth accepted for compatibility. - Members are role-gated; owners have full access.
- Non-associated callers receive
404 not-found(anti-enumeration).
Identifier policy
- Direct get/update/status calls require GUID/ID fields (
*_idor legacy*_guidwhere that is the canonical field name). Code-based lookups are resolve/search only. - Responses never include both
*_idand*_guidfor the same record (no dual-field output). - Exceptions (email-based UAS, PVM resolve, MRS
container+record_id) are listed in /common/ids-codes.html.
Request builder (canonical)
Headers (canonical)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"Field placement
- GET: query string (
orgcodecan be header or query; header is canonical). - POST: JSON body; body
session_guid/api_keyaccepted for compatibility.
GET template
bash
curl -sS -G "https://api.g3nretailstack.com/pvm/style/get" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "style_id=STYLE_ID"POST template
bash
curl -sS -X POST "https://api.g3nretailstack.com/pvm/style" \
-H "content-type: application/json" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
-d '{"code":"STYLE1","category_id":"CAT_ID","vendor_ids":["V1"],"manufacturer_ids":["M1"],"primary_vendor_id":"V1","primary_manufacturer_id":"M1"}'Field placement (GET vs POST)
- GET: parameters go in the query string; auth in headers (
x-session-guidorx-api-key). Includex-orgcode(ororgcodequery param). Do not send JSON bodies on GET. - POST: parameters go in the JSON body; auth in headers (
x-session-guidorx-api-key) is canonical. Bodysession_guid/api_keyis accepted for compatibility.orgcodeis required (header or body).
Retry guidance (endpoint class)
- GET reads/lists/resolves are safe to retry with identical inputs.
- POST writes/mutations should only be retried when you can tolerate duplicates or the endpoint uses
expected_revisionfor safe replay (refetch on 409).
Anti-enumeration (404)
Some org-scoped reads return 404 not-found when the caller is not associated with the org. This hides org existence from unrelated callers. Treat 404 as ambiguous (not found or not associated) and use the self-check flow in /common/troubleshooting.html.
Deployed: vendor/manufacturer CRUD + status; taxonomy (division/department/category/season) CRUD/status; Option Group/Option CRUD; OGM create/get/list/status; Style create/get/list/update/status/ogm-set; Variant create/get/list/status/update/stale-list/recreate; identifier add/transfer/history/resolve; alias set/remove/list/resolve; barcode add/set_primary/status/list/get/resolve; resolve/code; comments with presigned attachments + retention cleanup; comment abuse reporting; status history; kit components; alternatives; supplementary links; brand CRUD + brand↔supplier link management (style create/update optional brand_id with link enforcement when set).
CLI (API Gateway)
All commands require --session-guid and --orgcode (owners full access; member roles gate writes). Base defaults to https://api.g3nretailstack.com/pvm; override with --base-url. Note: brand commands require the /brand/* routes. Create commands: --code is optional where supported; pass --code-pattern / --code-max-attempts to auto-generate.
g3n pvm division-create --code DIV1 --orgcode ORG --session-guid ...g3n pvm department-create --code DEPT1 --division-code DIV1 --orgcode ORG --session-guid ...g3n pvm category-create --code CAT1 --department-code DEPT1 --parent-category-id <cat> --orgcode ORG --session-guid ...g3n pvm season-create --code SPR25 --orgcode ORG --session-guid ...g3n pvm brand-create --code BR1 --orgcode ORG --session-guid ...g3n pvm brand-create --code-pattern 'BR??' --code-max-attempts 16 --orgcode ORG --session-guid ...g3n pvm brand-link-add --brand-id <id> --supplier-type vendor --supplier-id <vendor> --orgcode ORG --session-guid ...g3n pvm option-group-create --code COLOR --supplier-scope '{\"vendor_ids\":[\"V1\"],\"manufacturer_ids\":[\"M1\"]}' --orgcode ORG --session-guid ...g3n pvm option-create --code BLACK --group-code COLOR --orgcode ORG --session-guid ...g3n pvm ogm-create --code ABC --groups '[{\"group_code\":\"COLOR\",\"priority\":1},{\"group_code\":\"SIZE\",\"priority\":2}]' --orgcode ORG --session-guid ...g3n pvm ogm-clone --ogm-id <ogm> --from-rev 1 --orgcode ORG --session-guid ...g3n pvm style-create --code SHOE1 --category-id <category_id> --ogm-id <ogm> --orgcode ORG --session-guid ...g3n pvm style-update --style-id <id> --expected-revision <rev> --body '{"brand_id":"BRAND1","vendor_ids":["V1"]}' --orgcode ORG --session-guid ...g3n pvm style-ogm-set --style-id <id> --expected-revision <rev> --ogm-id <ogm> --ogm-rev 2 --orgcode ORG --session-guid ...g3n pvm variant-create --style-id <id> --selections '[{\"group_code\":\"SIZE\",\"option_code\":\"W32L34\",\"size\":{\"waist\":32,\"inseam\":34}}]' --orgcode ORG --session-guid ...g3n pvm variant-update --style-id <id> --variant-id <id> --expected-revision <rev> --body '{\"sku\":\"SKU123\",\"service_flag\":true}' --orgcode ORG --session-guid ...g3n pvm variant-stale-list --style-id <id> --orgcode ORG --session-guid ...g3n pvm identifier-add --style-id <id> --variant-id <id> --type sku --value SKU123 --orgcode ORG --session-guid ...g3n pvm alias-set --style-id <id> --variant-id <id> --tag color --value red --orgcode ORG --session-guid ...g3n pvm barcode-add --style-id <id> --variant-id <id> --value 12345670 --scheme gtin --packaging-level each --orgcode ORG --session-guid ...g3n pvm barcode-set-primary --style-id <id> --variant-id <id> --barcode-id <id> --expected-revision <rev> --orgcode ORG --session-guid ...g3n pvm barcode-resolve --value 12345670 --orgcode ORG --session-guid ...g3n pvm resolve-code --code SHOE1 --orgcode ORG --session-guid ...g3n pvm history-status --target-type variant --target-id <id> --orgcode ORG --session-guid ...g3n pvm comment-report --limit 10 --orgcode ORG --session-guid ...- Generic helpers:
g3n pvm post --path variant/update --body '{...}' --orgcode ORG --session-guid ...andg3n pvm get --path variant/get --query '{\"variant_id\":\"VARIANT_ID\",\"orgcode\":\"ORG\"}' --session-guid .... Note: CLI--queryaccepts JSON; the HTTP GET equivalent uses query parameters (see examples below).
GET examples (query params)
These examples show canonical placement for GET parameters (query string) and auth (headers).
sh
curl -sS -G "https://api.g3nretailstack.com/pvm/division" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "status=active"
curl -sS -G "https://api.g3nretailstack.com/pvm/division/get" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "division_id=DIVISION_ID"
curl -sS -G "https://api.g3nretailstack.com/pvm/vendor" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "status=verified"
curl -sS -G "https://api.g3nretailstack.com/pvm/vendor/get" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "vendor_id=VENDOR_ID"
curl -sS -G "https://api.g3nretailstack.com/pvm/style" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "status=active" \
--data-urlencode "brand_id=BRAND_ID"
curl -sS -G "https://api.g3nretailstack.com/pvm/style/get" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "style_id=STYLE_ID"
curl -sS -G "https://api.g3nretailstack.com/pvm/variant/list" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "style_id=STYLE_ID" \
--data-urlencode "status=active"
curl -sS -G "https://api.g3nretailstack.com/pvm/variant/get" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "variant_id=VARIANT_ID"
curl -sS -G "https://api.g3nretailstack.com/pvm/resolve/code" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "code=STYLE1"
curl -sS -G "https://api.g3nretailstack.com/pvm/barcode/resolve" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "value=012345678905"
curl -sS -G "https://api.g3nretailstack.com/pvm/comment/list" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "target_type=style" \
--data-urlencode "target_id=STYLE_ID" \
--data-urlencode "limit=8"
curl -sS -G "https://api.g3nretailstack.com/pvm/comment/report" \
-H "x-orgcode: $ORGCODE" \
-H "x-session-guid: $SESSION_GUID" \
--data-urlencode "limit=10"Taxonomy (division/department/category/season)
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"- Codes immutable and org-scoped. Status
active|inactive|doomed; edits only while inactive. - Category hierarchy: optional
parent_category_idto form a tree inside a department. Enforced: acyclic, max depth 16, and parent must be in the same department.
Division
POST /division— create (inactive). Req:code, optionalcaption. Res:{ division_id, code, status=inactive }. PMA/owner only.GET /division— list bystatus(defaultactive), optionaltext, paginated. Res:{ items, next_token }.GET /division/get— bydivision_id(code via/resolve/code). Res: division.POST /division/update— updatecaption(inactive-only). PMA/owner only.POST /division/status— status change; dooming blocked while any non-doomed departments exist. PMA/owner only.
Department
POST /department— create (inactive). Req:codeanddivision_id(ordivision_code), optionalcaption. Res:{ department_id, division_id, code, status=inactive }. PMA/owner only.GET /department— list bydivision_id(ordivision_code) andstatus(defaultactive), optionaltext, paginated. Res:{ items, next_token }.GET /department/get— bydepartment_id(code via/resolve/code). Res: department.POST /department/update— updatecaption(inactive-only). PMA/owner only.POST /department/status— status change; dooming blocked while any non-doomed categories exist. PMA/owner only.
Category
POST /category— create (inactive). Req:codeanddepartment_id(ordepartment_code), optionalparent_category_id, optionalcaption. Res:{ category_id, department_id, division_id, code, status=inactive }. PMA/owner only.GET /category— list bydepartment_id(ordepartment_code) andstatus(defaultactive), optionalparent_category_idorroot_only=true, optionaltext, paginated. Res:{ items, next_token }.GET /category/get— bycategory_id(code via/resolve/code). Res: category.POST /category/update— updatecaptionand/orparent_category_id(inactive-only; reparent requires the category be a leaf). PMA/owner only.POST /category/status— status change; dooming blocked while any non-doomed child categories exist. PMA/owner only.
Season
POST /season— create (inactive). Req:code, optionalcaption. Res:{ season_id, code, status=inactive }. PMA/owner only.GET /season— list bystatus(defaultactive), optionaltext, paginated. Res:{ items, next_token }.GET /season/get— byseason_id(code via/resolve/code). Res: season.POST /season/update— updatecaption(inactive-only). PMA/owner only.POST /season/status— status change. PMA/owner only.
Style
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /style— create (inactive). Req:code, suppliers (vendor_ids[], manufacturer_ids[], primaries; each list capped at 128 with “oldest non-primary evicted” semantics), optionalogm_id/ogm_rev(if rev omitted, uses latest), optional defaultstemplate_econ/tax_hints/compliance/uom_dims_defaults/lifecycle_defaults(inherit from OGM when omitted; if provided partially, missing keys are filled from OGM), optionalbrand_id(must be linked to all referenced vendors/manufacturers). Res:{ style_id, code, status=inactive }.GET /style— list with filters (status,brand_id,vendor_id,manufacturer_id,category_id,text), paginated. Res:{ items, next_token }.GET /style/get— bystyle_id(code via/resolve/code). Res: style.POST /style/update— inactive-only updates (suppliers/category/defaults/lifecycle_defaults/aliases/seasons, optionalbrand_id). Res: style. Note: OGM changes usePOST /style/ogm/set.POST /style/status— set status; doom blocked while non-doomed variants exist. Res: style.
Suppliers (vendor/manufacturer)
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /vendor— create (unverified). Req:code, optionalcaption. Res:{ vendor_id, code, status=unverified }. VCA/owner only.GET /vendor— list bystatus(defaultverified), optionaltext, paginated. Res:{ items, next_token }.GET /vendor/get— byvendor_id(code via/resolve/code). Res: vendor.POST /vendor/update— updatecaption(code immutable). VCA/owner only.POST /vendor/status— status transition (unverified|verified|suspended|archived|doomed). VCA/owner only.- Manufacturer mirrors vendor:
POST /manufacturer,GET /manufacturer(optionaltext),GET /manufacturer/get(id only; code via/resolve/code),POST /manufacturer/update,POST /manufacturer/status.
Brand
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /brand— create (inactive). Req:code, optionalcaption. Res:{ brand_id, code, status=inactive }. PMA/owner only.GET /brand— list bystatus(defaultactive), optionaltext, paginated. Res:{ items, next_token }.GET /brand/get— bybrand_id(code via/resolve/code). Res: brand.POST /brand/update— updatecaption(inactive-only). PMA/owner only.POST /brand/status— status change. When settingstatus=active, primary links are required when supplier links exist (set viaPOST /brand/link/set_primary). PMA/owner only.POST /brand/link/add— link a brand to a supplier. Req:{ brand_id, supplier_type (vendor|manufacturer), supplier_id }. PMA/owner only.POST /brand/link/remove— unlink a brand from a supplier. Req:{ brand_id, supplier_type, supplier_id, expected_revision }. PMA/owner only.POST /brand/link/set_primary— set the brand’s primary supplier forsupplier_type. Req:{ brand_id, supplier_type, supplier_id, expected_revision? }. If a primary already exists for thissupplier_type,expected_revisionis required. PMA/owner only.GET /brand/link/list— list links bybrand_id(optionalsupplier_typefilter) or bysupplier_type+supplier_id, paginated. Res:{ items, next_token }.
Option Groups
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /option_group— create (inactive). Req:code?,caption?,vendor_code?,manufacturer_code?,vendor_caption?,manufacturer_caption?,supplier_scope?(verified suppliers only). Res:{ option_group_id, code, status=inactive }(and includesnormalized_caption).GET /option_group— list bystatus(defaultactive), optionaltext, paginated. Res:{ items, next_token }.GET /option_group/get— byoption_group_id(code via/resolve/code). Res: option group.POST /option_group/update— inactive-only updates (caption/supplier_scope/supplier metadata; code immutable). Requiresexpected_revision. Lookup byoption_group_id. Res: option group.POST /option_group/status— status change. Requiresexpected_revision. Lookup byoption_group_id. Res: option group.
Options
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /option— create (inactive). Req:group_code|option_group_id,code?,caption?,vendor_code?,manufacturer_code?,vendor_caption?,manufacturer_caption?,size?(optional{ waist, inseam, cup, width }). Res:{ option_id, option_group_id, code, status=inactive }(and includesnormalized_caption).GET /option— list bystatus(defaultactive), optionaltext, paginated. Optionally scope to a group usingoption_group_idorgroup_code. Res:{ items, next_token }.GET /option/get— byoption_id+option_group_id(code via/resolve/code). Res: option.POST /option/update— inactive-only updates (caption/supplier metadata/size; code immutable). Requiresexpected_revision. Lookup byoption_id+option_group_id. Res: option.POST /option/status— status change. Requiresexpected_revision. Lookup byoption_id+option_group_id. Res: option.
OGM (revisioned defaults; option matrix)
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /ogm— create revisioned OGM (immutable per rev). Optional defaults:template_econ/tax_hints/compliance/uom_dims_defaults/lifecycle_defaults(UOM dims requirebase_uom;sell_uomdefaults toea;conversion_factordefaults to 1 whensell_uom==base_uom, otherwise required; ifcontrol_typeisserial/both, thenserial_modeis required; dims/weights normalize to canonical mm/g and preserve optionalsource_uom/source_weight_uom/shipping/package equivalents). Res:{ ogm_id, ogm_rev }.POST /ogm/clone— new rev fromfrom_rev(optional new code) with optional override/clear of defaults. Res: ogm.GET /ogm/get— fetch rev (current ifogm_revomitted). Res: ogm.GET /ogm/list— filters status/text, paginated. Res:[ogm], next_token.POST /ogm/status— status change; doom blocked when in use. Res: ogm.
Variants
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /variant— create (inactive). Req:style_id,selections[](group_code/option_code, optionalsize{waist, inseam, cup, width}), optionalcode,template_overrides,service_flag,kit_flag,lifecycle. Selections are resolved/validated against Option Group/Option records; if the style’s OGM hasgroups[], selections must include exactly one per group. Signature uniqueness per style is enforced among non-doomed variants; signature is built in OGM group order fromgroup_code=option_codeonly (size ignored). Size values are stored as-provided (no unit conversion); interpretation is domain-specific. Inherits defaults from style (with OGM fallback), and appliestemplate_overrides/lifecycleas overlays to produce effectivetemplate_econ/tax_hints/compliance/uom_dims/lifecycleon the variant record. Compliance overlay extends nested collections:certifications[]are unioned andattributes{}keys are merged (variant values win per-key); objects likerecall,stop_sale,restricted_distribution,delivery_constraints,allergens,ingredients, andlabels_by_marketare merged by defined keys.recall.status=activeorstop_sale.status=activeforcesis_sellable_now=false. Service constraint: ifservice_flag=true, thenuom_dims.base_uomanduom_dims.sell_uommust beservice_unitorhour(and conversely, using service UOMs requiresservice_flag=true). UOM dims store canonical mm/g and preserve optionalsource_uom/source_weight_uom/shipping/package equivalents (see OpenAPI/PROTOCOL). Res:{ variant_id, style_id, code?, signature }.GET /variant/list— filters (status, optionalstyle_id, style filtersbrand_id/vendor_id/manufacturer_id/category_id, variant filterscontrol_type/service_flag/kit_flag, optionaltext), paginated. Res:{ items, next_token }(stale included; items include derivedis_sellable_now).GET /variant/get— byvariant_id(optionalstyle_idhint; code via/resolve/code). Res: variant (signature, matrix_rev, stale) plus derivedis_sellable_now.POST /variant/update— inactive-only updates (caption, sku, service_flag, kit_flag, lifecycle, template_overrides). Req:style_id,variant_id. Res: variant (includesis_sellable_now).POST /variant/status— status set; doom blocked while outbound kit components/alternatives/supplementary links exist, and while active inbound links exist. Req:style_id,variant_id,status. Res: variant (includesis_sellable_now).GET /variant/stale/list— list stale variants for a style, paginated. Res:{ items, next_token }(items includeis_sellable_now).POST /variant/recreate— recreate a variant to a target OGM rev (inactive-only) and recompute staleness. Requiresexpected_revision. Res:{ source_variant_id, target_ogm_rev, variant }(variant includesis_sellable_now).
Identifiers & Aliases
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /identifier/add— add/rotate current identifier; logs history; enforces uniqueness. Res: identifier state withcurrent=trueand optionalreplaced_identifierwhen a prior current value is retired to “past”.POST /identifier/transfer— move identifier to another variant (requiresexpected_revision+reason). Res: transfer result/history entry.GET /identifier/history— history chain by{type,value}, paginated. Res:{ items[], next_token }.GET /identifier/resolve— resolve{type,value}to current owner (optionalinclude_history). Res:{ type, value, current_owner, history?, history_next_token? }.GET /identifier/list— list identifiers for a variant (optionaltypefilter), paginated. Includescurrentboolean per item. Default excludes transferred identifiers; setinclude_transferred=trueto include.POST /alias/set— set tag/value (≤16 per record, unique per org). Inactive-only. Res: alias list.POST /alias/remove— remove tag/value (inactive-only). Res: alias list.GET /alias/list— aliases for variant, paginated. Res: alias list +next_token.GET /resolve/alias— resolve tag/value to variant.
Barcodes
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /barcode/add— digits-only with valid check digit;(org,value)unique among non-doomed; reuse only if prior inactive/doomed ANDallow_reuse=true+reason(reason required when reassigning to a different variant); otherwise 409. Fields:scheme(gtin/upc-a/ean-13/ean-8/itf-14/gs1-128/code128/qr/other, default gtin),packaging_level(each/inner_pack/case/pallet/display/other, default each),issued_by?(gs1/vendor/org/unknown, default unknown),caption?,allow_reuse?,reason?. Res: barcode.POST /barcode/set_primary— one primary per packaging_level; barcode must belong to variant. Res: primary result.POST /barcode/status— status change. Res: barcode.GET /barcode/list— by variant, paginated. Res:[barcode], next_token.GET /barcode/get— by barcode_id. Res: barcode.GET /barcode/resolveor/resolve/barcode— resolve value → owner variant; rejects invalid check digit. Res:{ barcode, owner }.
Staleness & migration
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /style/ogm/set— switch style to new ogm_rev; marks variants on older matrix_rev as stale. Res:{ style_id, ogm_id, ogm_rev, previous_ogm_rev, stale_variant_ids[] }.GET /variant/stale/list— stale variants for style, paginated. Res:[variant], next_token.POST /variant/recreate— rebuild variant on target OGM rev using current selections; fails if selections invalid on target rev. Res:{ source_variant_id, target_ogm_rev, variant }.
Comments (PVM-only)
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /comment— add immutable comment with optionalparent_comment_idand attachments (≤128 MB each,{filename, content_type?, size_bytes, md5_hex?}). Returns{ comment_id, attachments[] { attachment_id, upload_url, key } }for presigned uploads (MD5 must be base64 in headers when provided).GET /comment/list— filter by{ target_type,target_id }oruser_guid, optionalstatus(current/archived/doomed), paginated. Returns comments; wheninclude_urls(default true) presigned download URLs are included for attachments (unless attachments were cleaned up).POST /comment/status— archive/doomed (content immutable). PMA/owner only.GET /comment/report— PMA/owner only; report top-N largest comments by attachment size (abuse detection). Query:limit(1–100, default 10). Returns[{ comment_id, target_type, target_id, total_size_bytes, attachment_count, created_at }]. Note: no backfill; only comments with rollups are included.- Attachment retention: attachments are subject to cleanup after retention (defaults: current 90d from creation; archived 30d after archive; doomed 7d after doom). When cleaned up,
comment/listomits download URLs and the comment includesattachments_deleted_at.
Kits / Links
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"
-H "content-type: application/json"POST /kit/component/add— add kit component (qty, uom, notes) to kit variant (must havekit_flagand be inactive). PMA/owner only.POST /kit/component/remove— remove a component (kit must be inactive).GET /kit/component/list— list components for a kit variant, paginated.POST /alternative/add— add alternative link between variants (priority, auto_select, requires_confirmation, is_replacement, start/end, notes). Source variant must be inactive.POST /alternative/status— set status (active/inactive/doomed) for alternative link.GET /alternative/list— list alternatives for a variant (optional status filter), paginated.POST /supplementary/add— add supplementary link (kind upsell/cross_sell/accessory/consumable/warranty/service_addon/look/bundle_suggestion/other, priority, auto_suggest, start/end, notes). Source variant must be inactive.POST /supplementary/status— set status for supplementary link.GET /supplementary/list— list supplementary links for a variant (optional status/kind filters), paginated.
Resolve
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"GET /resolve/code— resolve a code across entities (priority order) and return{ code, target_type, target_id }.
History
Canonical curl headers (API Gateway)
bash
-H "x-orgcode: $ORGCODE"
-H "x-session-guid: $SESSION_GUID" # or: -H "x-api-key: $API_KEY"GET /history/status— status transitions for{ target_type (style|variant), target_id }, paginated. Res:items[]withfrom_status,to_status,timestamp,actor,reason+next_token. Note: no backfill; only transitions recorded after deploy are included.
Error tags (representative)
invalid-input, forbidden, unauthorized, not-found, conflict (code/alias/identifier/barcode/signature), invalid-state (active edits, supplier/OGM mismatch), invalid-check-digit, code-generation-exhausted, throttled, internal-error.
Error envelope example (canonical)
json
{
"success": false,
"error": {
"error_code": "pvm.conflict_revision",
"http_status": 409,
"retryable": false,
"request_id": "req-123",
"trace_id": "trace-abc",
"major": { "tag": "conflict", "message": { "en_US": "Expected revision does not match the current record." } },
"details": { "expected_revision": "3", "current_revision": "4" },
"conflict_snapshot": { "revision": 4 }
},
"build": { "...": "..." },
"stats": { "call": "example", "service": "pvm", "timestamp_utc": "2026-01-21T00:00:00Z", "request_id": "req-123" }
}Role requirements (by endpoint family)
- Read/list/resolve/comments:
pvm_view(aliases:pvv) or owner. - Product model writes (taxonomy, style, variant, ogm):
pvm_edit(aliases:pma) or owner. - Supplier writes (vendor/manufacturer):
pvm_supplier_admin(alias:vca) or owner. - Approvals (if present):
pvm_approveor owner.
Idempotency & retries
- Creates are not idempotent; prefer caller-provided
codeand verify before retrying. - Updates require
expected_revision; re-read on409/428before retrying.
Performance notes (list filters)
- No DynamoDB Scan operations are used in PVM list handlers; lists use Query against tenant-scoped PKs/GSIs or derived index items.
- List endpoints return summary fields only (reduced payload/RCU). Use the matching
*/getendpoint for full record detail. - Supplier/brand/option/taxonomy list
textsearches use the tokenized search plane (GSI15) and are eventually consistent; orgs can opt into OpenSearch via OFMsearch_plane.pvm=opensearch(events recordsearch_plane+ fallback). GET /style:- Code-like
textuses the STYLE_CODE index (GSI1) with status/filters applied as FilterExpression. - Non-code
text(contains) and some filters use the status-bucket index (GSI2) with FilterExpression.
- Code-like
GET /variant/list:- With
style_id, the style partition is queried and filters are applied via FilterExpression. - Without
style_id, primary filters (brand/vendor/manufacturer/category) use the derivedvariant_filterindex; low-selectivity flags (control_type,service_flag,kit_flag) and non-codetextare post-filtered (FilterExpression).
- With
- For large orgs, prefer code-prefix searches and high-selectivity filters; keep
limitsmall and paginate.
Common pitfalls
- Edits are inactive-only; active records require status change first.
- Supplier verification is required before style creation.
- Brand links must cover all referenced suppliers when
brand_idis set.
Examples (core families)
Style create
json
{ "code": "TEE", "category_id": "CATEGORY_ID", "vendor_ids": ["VENDOR_ID"], "manufacturer_ids": ["MFG_ID"], "primary_vendor_id": "VENDOR_ID", "primary_manufacturer_id": "MFG_ID" }Response (shape):
json
{ "success": true, "data": { "style_id": "STYLE_ID" }, "build": { "...": "..." }, "stats": { "...": "..." } }Barcode add
json
{ "style_id": "STYLE_ID", "variant_id": "VARIANT_ID", "value": "012345678905", "scheme": "gtin", "packaging_level": "each" }Response (shape):
json
{ "success": true, "data": { "barcode_id": "BARCODE_ID" }, "build": { "...": "..." }, "stats": { "...": "..." } }