Completeness

Measure a product against what its family requires, and read a family's whole field list in one call with GET /v1/products/attribute-schema.

Two routes, and they belong together: one tells you what a product should carry, the other how much of it it does.

The field list in one call

The catalog's shape is tenant data spread over six entities. Reading it by hand means five requests, a join, and a private attributes.type → input mapping in every client — and that mapping is exactly the part that must not be private, because the type list carries no fixed value set and integrators extend it.

GET /v1/products/attribute-schema answers one ready-to-render field list instead, ordered by group and then by the family's own ordering. It writes nothing.

Request
curl "https://api.revenexx.com/v1/products/attribute-schema?family_code=power_tools" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

Per field it reports:

  • the attribute code, its resolved label and its type;
  • its group, so a form can lay out sections;
  • storage.path — the exact path inside attribute_values a value belongs at;
  • from — the full fallback order a reader should follow;
  • the republished validation keys a client can act on: min, max, min_length, max_length, pattern, min_items, max_items;
  • whether the field is required, and why it may be read-only (an attribute that is a variant axis is read-only on the model).

An unknown type is mapped onto a text field rather than refusing to answer, so a tenant that models a fifth type still gets a renderable form.

Without a family it answers every attribute declared for the given entity_type / entity_ref — the shape of a reference entity's records or an asset family, which have attributes but no family:

Request
curl "https://api.revenexx.com/v1/products/attribute-schema?entity_type=reference_entity&entity_ref=brand" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

Completeness

POST /v1/products/{id}/completeness computes how much of what the product's family requires it actually carries, and stores the result in products.completeness.

Request
curl -X POST "https://api.revenexx.com/v1/products/{id}/completeness" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{}'

There is no body to send — everything it needs is the path id and what the catalog already holds.

The stored document:

KeyMeaning
requiredHow many attributes the family marks is_required.
filledHow many of those carry a value in any bucket.
ratiofilled / required, between 0 and 1. A family that requires nothing is 1, not undefined.
missingThe codes with no value anywhere, sorted.
computed_atWhen this measurement was taken.

It measures family_attributes.is_required and nothing else. Remember that required_channels of null or [] means required everywhere — see Data model.

A product with no family answers 400

A product with no family has no required attributes, so there is nothing to measure it against — and the app answers 400 rather than an invented 0 %.

Assign a family first, which computes and stores the completeness in the same call:

Request
curl -X POST "https://api.revenexx.com/v1/products/{id}/family" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"family_code":"power_tools"}'

That is the step every family-driven surface waits on: without a family, completeness cannot be computed and the family's label_attribute never resolves.

Where the stored value shows up

products.completeness is a column, so it comes back on GET /v1/products/{id} and as a column in GET /v1/products/grid — a merchandiser's worklist is a grid sorted by it, not a route of its own. It is only as fresh as the last computation, so recompute after a bulk change.

Where to go next

Was this page helpful?