Skip to content

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-orgcode and x-session-guid (user session) or x-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 (*_id or legacy *_guid where that is the canonical field name). Code-based lookups are resolve/search only.
  • Responses never include both *_id and *_guid for 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 (orgcode can be header or query; header is canonical).
  • POST: JSON body; body session_guid / api_key accepted 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-guid or x-api-key). Include x-orgcode (or orgcode query param). Do not send JSON bodies on GET.
  • POST: parameters go in the JSON body; auth in headers (x-session-guid or x-api-key) is canonical. Body session_guid/api_key is accepted for compatibility. orgcode is 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_revision for 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 ... and g3n pvm get --path variant/get --query '{\"variant_id\":\"VARIANT_ID\",\"orgcode\":\"ORG\"}' --session-guid .... Note: CLI --query accepts 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_id to 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, optional caption. Res: { division_id, code, status=inactive }. PMA/owner only.
  • GET /division — list by status (default active), optional text, paginated. Res: { items, next_token }.
  • GET /division/get — by division_id (code via /resolve/code). Res: division.
  • POST /division/update — update caption (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: code and division_id (or division_code), optional caption. Res: { department_id, division_id, code, status=inactive }. PMA/owner only.
  • GET /department — list by division_id (or division_code) and status (default active), optional text, paginated. Res: { items, next_token }.
  • GET /department/get — by department_id (code via /resolve/code). Res: department.
  • POST /department/update — update caption (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: code and department_id (or department_code), optional parent_category_id, optional caption. Res: { category_id, department_id, division_id, code, status=inactive }. PMA/owner only.
  • GET /category — list by department_id (or department_code) and status (default active), optional parent_category_id or root_only=true, optional text, paginated. Res: { items, next_token }.
  • GET /category/get — by category_id (code via /resolve/code). Res: category.
  • POST /category/update — update caption and/or parent_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, optional caption. Res: { season_id, code, status=inactive }. PMA/owner only.
  • GET /season — list by status (default active), optional text, paginated. Res: { items, next_token }.
  • GET /season/get — by season_id (code via /resolve/code). Res: season.
  • POST /season/update — update caption (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), optional ogm_id/ogm_rev (if rev omitted, uses latest), optional defaults template_econ/tax_hints/compliance/uom_dims_defaults/lifecycle_defaults (inherit from OGM when omitted; if provided partially, missing keys are filled from OGM), optional brand_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 — by style_id (code via /resolve/code). Res: style.
  • POST /style/update — inactive-only updates (suppliers/category/defaults/lifecycle_defaults/aliases/seasons, optional brand_id). Res: style. Note: OGM changes use POST /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, optional caption. Res: { vendor_id, code, status=unverified }. VCA/owner only.
  • GET /vendor — list by status (default verified), optional text, paginated. Res: { items, next_token }.
  • GET /vendor/get — by vendor_id (code via /resolve/code). Res: vendor.
  • POST /vendor/update — update caption (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 (optional text), 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, optional caption. Res: { brand_id, code, status=inactive }. PMA/owner only.
  • GET /brand — list by status (default active), optional text, paginated. Res: { items, next_token }.
  • GET /brand/get — by brand_id (code via /resolve/code). Res: brand.
  • POST /brand/update — update caption (inactive-only). PMA/owner only.
  • POST /brand/status — status change. When setting status=active, primary links are required when supplier links exist (set via POST /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 for supplier_type. Req: { brand_id, supplier_type, supplier_id, expected_revision? }. If a primary already exists for this supplier_type, expected_revision is required. PMA/owner only.
  • GET /brand/link/list — list links by brand_id (optional supplier_type filter) or by supplier_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 includes normalized_caption).
  • GET /option_group — list by status (default active), optional text, paginated. Res: { items, next_token }.
  • GET /option_group/get — by option_group_id (code via /resolve/code). Res: option group.
  • POST /option_group/update — inactive-only updates (caption/supplier_scope/supplier metadata; code immutable). Requires expected_revision. Lookup by option_group_id. Res: option group.
  • POST /option_group/status — status change. Requires expected_revision. Lookup by option_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 includes normalized_caption).
  • GET /option — list by status (default active), optional text, paginated. Optionally scope to a group using option_group_id or group_code. Res: { items, next_token }.
  • GET /option/get — by option_id + option_group_id (code via /resolve/code). Res: option.
  • POST /option/update — inactive-only updates (caption/supplier metadata/size; code immutable). Requires expected_revision. Lookup by option_id + option_group_id. Res: option.
  • POST /option/status — status change. Requires expected_revision. Lookup by option_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 require base_uom; sell_uom defaults to ea; conversion_factor defaults to 1 when sell_uom==base_uom, otherwise required; if control_type is serial/both, then serial_mode is required; dims/weights normalize to canonical mm/g and preserve optional source_uom/source_weight_uom/shipping/package equivalents). Res: { ogm_id, ogm_rev }.
  • POST /ogm/clone — new rev from from_rev (optional new code) with optional override/clear of defaults. Res: ogm.
  • GET /ogm/get — fetch rev (current if ogm_rev omitted). 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, optional size {waist, inseam, cup, width}), optional code, template_overrides, service_flag, kit_flag, lifecycle. Selections are resolved/validated against Option Group/Option records; if the style’s OGM has groups[], selections must include exactly one per group. Signature uniqueness per style is enforced among non-doomed variants; signature is built in OGM group order from group_code=option_code only (size ignored). Size values are stored as-provided (no unit conversion); interpretation is domain-specific. Inherits defaults from style (with OGM fallback), and applies template_overrides / lifecycle as overlays to produce effective template_econ/tax_hints/compliance/uom_dims/lifecycle on the variant record. Compliance overlay extends nested collections: certifications[] are unioned and attributes{} keys are merged (variant values win per-key); objects like recall, stop_sale, restricted_distribution, delivery_constraints, allergens, ingredients, and labels_by_market are merged by defined keys. recall.status=active or stop_sale.status=active forces is_sellable_now=false. Service constraint: if service_flag=true, then uom_dims.base_uom and uom_dims.sell_uom must be service_unit or hour (and conversely, using service UOMs requires service_flag=true). UOM dims store canonical mm/g and preserve optional source_uom/source_weight_uom/shipping/package equivalents (see OpenAPI/PROTOCOL). Res: { variant_id, style_id, code?, signature }.
  • GET /variant/list — filters (status, optional style_id, style filters brand_id/vendor_id/manufacturer_id/category_id, variant filters control_type/service_flag/kit_flag, optional text), paginated. Res: { items, next_token } (stale included; items include derived is_sellable_now).
  • GET /variant/get — by variant_id (optional style_id hint; code via /resolve/code). Res: variant (signature, matrix_rev, stale) plus derived is_sellable_now.
  • POST /variant/update — inactive-only updates (caption, sku, service_flag, kit_flag, lifecycle, template_overrides). Req: style_id, variant_id. Res: variant (includes is_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 (includes is_sellable_now).
  • GET /variant/stale/list — list stale variants for a style, paginated. Res: { items, next_token } (items include is_sellable_now).
  • POST /variant/recreate — recreate a variant to a target OGM rev (inactive-only) and recompute staleness. Requires expected_revision. Res: { source_variant_id, target_ogm_rev, variant } (variant includes is_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 with current=true and optional replaced_identifier when a prior current value is retired to “past”.
  • POST /identifier/transfer — move identifier to another variant (requires expected_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 (optional include_history). Res: { type, value, current_owner, history?, history_next_token? }.
  • GET /identifier/list — list identifiers for a variant (optional type filter), paginated. Includes current boolean per item. Default excludes transferred identifiers; set include_transferred=true to 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 AND allow_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/resolve or /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 optional parent_comment_id and 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 } or user_guid, optional status (current/archived/doomed), paginated. Returns comments; when include_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/list omits download URLs and the comment includes attachments_deleted_at.

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 have kit_flag and 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[] with from_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_approve or owner.

Idempotency & retries

  • Creates are not idempotent; prefer caller-provided code and verify before retrying.
  • Updates require expected_revision; re-read on 409/428 before 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 */get endpoint for full record detail.
  • Supplier/brand/option/taxonomy list text searches use the tokenized search plane (GSI15) and are eventually consistent; orgs can opt into OpenSearch via OFM search_plane.pvm=opensearch (events record search_plane + fallback).
  • GET /style:
    • Code-like text uses 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.
  • 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 derived variant_filter index; low-selectivity flags (control_type, service_flag, kit_flag) and non-code text are post-filtered (FilterExpression).
  • For large orgs, prefer code-prefix searches and high-selectivity filters; keep limit small 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_id is 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": { "...": "..." } }