Pre-launch API. The concepts described here are settled, but the API shape is not: request and response fields, parameters and defaults can still change. Build against it, and talk to your Luigi's Box contact before you put an integration into production.
Source data carries its own names and shapes: name instead of @title, price_with_vat
as a string instead of price as a number, availability as "in stock" instead of 1.
A mapping is a small declarative document that says which source field becomes which catalog field, and what to do to the value on the way. Nothing about your export or your payloads has to change.
Mapping sits in front of both ways in: content arriving from a feed and content pushed over the Content API are mapped the same way, by object type.
Mappings are usually written with your Luigi’s Box implementation contact during onboarding. This page is for reading the one you have, and for changing it yourself over the feed management API.
A mapping is a JSON object of source → target:
{ "name": "@title", "product_id": "@id", "price_with_vat": "price | to_f", "stock_status": "availability | translate('in stock', 1, 'out of stock', 0) | to_i"}- The key selects a field on the incoming record. Dot-paths reach into nested
structures;
*matches a group of names. - The value is a target spec: an optional target field name, then any number of
|-chained transforms.
Fields you do not mention pass through unchanged, so a mapping only needs to describe what differs.
Renaming
Section titled “Renaming”The target spec is the new name:
{ "name": "@title", "product_id": "@id", "link": "url", "img": "image_url"}Transforming
Section titled “Transforming”Chain transforms after a |. They run left to right.
{ "price_with_vat": "price | to_f", "description": "description | decode_html_entities", "link": "url | strip_all_parameters"}To transform a field in place, without renaming it, omit the target name and start with the pipe:
{ "price": "| to_f", "title": "| decode_html_entities"}Applied to a list, a transform runs on each element. Transforms after |< run once, on the
whole list the transforms before them produced. A spec holds at most one |<; chain the
transforms after it with | as usual.
{ "sizes": "| split_by_separator(',') |< first | unpack_array" }"S,M,L" becomes "S": without the |<, first would run on "S", "M" and "L" in turn.
Available transforms
Section titled “Available transforms”Type conversion. A field’s first value fixes its type for the whole catalog, and a price stored as text cannot be range-filtered.
| Transform | Effect |
|---|---|
to_f | To a number. 0.0 if the value is missing or unparseable. |
to_i | To a whole number, truncating decimals. 0 on failure; an infinite value, such as "inf" or "1e400", fails the mapping. |
to_s | To text. Empty string for a missing value. |
to_bool | To true/false. Accepts 1/0, y/n, yes/no, t/f, true/false. |
to_date | To an ISO date, YYYY-MM-DD. |
to_time | To an ISO 8601 timestamp. |
json_parse | Parse a JSON string into real structure. |
Text.
| Transform | Effect |
|---|---|
replace('find', 'with') | Replace every occurrence |
prefix('text') / append('text') | Add text at the start or end |
capitalize | First character upper, rest lower |
split_by_separator('/') | Split into a list, trimming each part |
decode_html_entities | Decode entities and strip HTML tags |
strip_all_parameters | Drop the query string from a URL |
translate('a', 1, 'b', 2) | Map values through a lookup table; unmatched values pass through |
Lists.
| Transform | Effect |
|---|---|
to_array | Wrap a single value in a list |
first / last | Keep only the first or last element |
drop_last | Drop the last element; on a list of lists, the last of each |
push('item') | Add an element at the end; on a list of lists, at the end of each |
flatten | Flatten one level of nesting |
uniq | Remove duplicates, keeping order |
unpack_array | Unwrap a one-element list to the value itself |
values | Take a nested object’s values as a list |
Numbers.
| Transform | Effect |
|---|---|
min / max | The smallest or largest of the value, optionally against a second value |
Category aliases. A name path names a category by the categories above it, such as
Home > Electronics > TV. Split it into its names with split_by_separator, then turn it into a
category alias after |<, so the transform
gets the whole path. This links the categories of a feed that names them by path — see
Category name paths.
| Transform | Effect |
|---|---|
to_alias | Turn a path into the category alias it spells: each name trimmed, lowercased and URL-encoded, joined with / after category/~. Home > Smart TV, split on >, becomes category/~home/smart%20tv. A field holding several paths gives one alias per path, each once, in order |
prefix is how you build typed identities from a bare key:
{ "product_id": "@id | prefix('product/')" }A source product_id of sku-1001 becomes @id: "product/sku-1001" — the form
identity requires.
Referencing another field
Section titled “Referencing another field”A transform argument beginning with @ reads another field from the same record:
{ "sale_price": "price | min(@list_price)" }If the referenced field is absent, the mapping fails for that record and the record is rejected with mapping_failed (see What gets rejected). Only a reference inside @defaults is skipped when the field is absent.
Writing to several fields
Section titled “Writing to several fields”Give a list of target specs to write one source field to several places — including back to its own name, which is how you keep a field while also deriving from it:
{ "name": ["@title", "name"], "category_path": ["@category | split_by_separator(' > ') | last", "category_path"]}Nested sources
Section titled “Nested sources”Dot-paths reach into structure:
{ "attributes.color": "color", "shipping.price": "shipping_price | to_f"}To name a key that itself contains a dot, escape the dot with a backslash, written twice in
JSON. This reads color from the key params.v2:
{ "params\\.v2.color": "color" }A . as the target name spreads an object’s keys into the record itself, which flattens a
nested block:
{ "params": "." }Applied to a record:
// before{ "params": { "material": "cotton", "fit": "regular" } }
// after{ "material": "cotton", "fit": "regular" }Wildcards
Section titled “Wildcards”* matches a group of source names, and \1 in the target inserts what it matched:
{ "custom_*": "\\1" }custom_color becomes color, custom_fit becomes fit. Useful for feeds that prefix
every extra attribute.
Defaults
Section titled “Defaults”An @defaults block fills in fields the record does not have. It runs before the rest of
the mapping, and it never overwrites a value that is already present:
{ "@defaults": { "availability": 1, "currency": "EUR" }, "price_with_vat": "price | to_f"}Values here can reference other fields with @, and a reference to an absent field is
skipped.
Try it before you save
Section titled “Try it before you save”POST /catalog/v1/{catalog_id}/mappings/validate applies a mapping to a sample record and
returns the result, with a trace of what every rule did to it. Nothing is stored.
curl -X POST 'https://api.eu1.luigisbox.ai/catalog/v1/lbc_8w3k2p/mappings/validate' \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -d '{ "mapping": { "name": "@title", "product_id": "@id | prefix(\"product/\")", "price_with_vat": "price | to_f", "stock_status": "availability | translate(\"in stock\", 1, \"out of stock\", 0) | to_i" }, "sample": { "product_id": "sku-1001", "name": "Blue Cotton T-Shirt", "price_with_vat": "29.90", "stock_status": "in stock" } }'The response is the mapped record, whether the mapping fails on the sample, and one
entry per rule — here cut to the stock_status rule:
{ "result": { "@title": "Blue Cotton T-Shirt", "@id": "product/sku-1001", "price": 29.9, "availability": 1 }, "rejection": null, "rules": [ { "key": "stock_status", "status": "applied", "matches": [ { "source_path": [ "stock_status" ], "source_value": "in stock", "targets": [ { "target_specification": "availability | translate(\"in stock\", 1, \"out of stock\", 0) | to_i", "transforms": [ { "name": "translate", "arguments": [ "in stock", 1, "out of stock", 0 ], "per_element": false, "value": 1 }, { "name": "to_i", "arguments": [], "per_element": false, "value": 1 } ], "value": 1, "status": "written", "target_paths": [ [ "availability" ] ], "problem": null } ] } ] } ]}rules lists every rule in the order written, @defaults first. Each match is one field the
key selected — several for a wildcard or a key inside a list, none for a key the sample does not
have — and each target lists the value after every transform and the paths of the fields of
result it wrote. Paths hold object keys as strings and list positions as integers, as they
were when the rule ran: a rule that drops an emptied list element shifts the positions after it,
so a later lookup in result can land on a different element. After an append (+) the field
in result holds more than the target’s own value.
per_element is true for a transform that ran on each element of a list.
Rule status | Meaning |
|---|---|
applied | The key matched and no target failed |
no_match | The sample has no field the key selects |
failed | A target failed on the sample |
Target status | Meaning |
|---|---|
written | The value was written to target_paths |
skipped_source_null | The matched field is null; nothing is written and the field is dropped |
skipped_value_present | An @defaults entry for a field the record already has |
skipped_reference_missing | An @defaults entry referencing a field the record does not have |
discarded | The target path cannot be reached — it runs through a value that is not an object — so the value is dropped and nothing is written; no error is raised |
failed | The target failed; problem says why |
In an @defaults row, target_specification is the field being defaulted and source_path is the
one-element path of the field it references, or null for a literal value. A skipped entry
writes nothing.
To see only the mapped record, add "trace": false to the request body: the response keeps
result and rejection, and rules comes back empty.
Use one record copied verbatim from your real feed as the sample. A hand-written example can differ from the export in exactly the detail the mapping has to handle.
When a rule fails on the sample
Section titled “When a rule fails on the sample”A rule that fails on the sample still answers 200: the mapping is valid, and a record like
the sample is what fails. rejection carries the message ingest rejects such a record with,
under mapping_failed (see What gets rejected).
Ingest shortens a message longer than 80 characters, ending it with …; /validate returns it in full. Every other rule still runs, so
one request shows every failure.
Only the mapping runs here. A null rejection does not mean the record passes the checks ingest
makes after mapping, such as title_missing or namespace_forbidden — see
What gets rejected.
This request references @list_price, which the sample does not have:
{ "mapping": { "sale_price": "price | min(@list_price)" }, "sample": { "sale_price": "24.90", "name": "Blue Cotton T-Shirt" }}The failed target is marked failed. result no longer holds sale_price, and name, which no rule maps, passes through. Ingest would reject a record like this, so none of it would be stored:
{ "result": { "name": "Blue Cotton T-Shirt" }, "rejection": { "code": "mapping_failed", "message": "Tried to reference @list_price but such attribute is not present" }, "rules": [ { "key": "sale_price", "status": "failed", "matches": [ { "source_path": [ "sale_price" ], "source_value": "24.90", "targets": [ { "target_specification": "price | min(@list_price)", "transforms": [ { "name": "min", "arguments": [ "@list_price" ], "per_element": false, "value": null } ], "value": null, "status": "failed", "target_paths": [], "problem": { "code": "mapping_reference_missing", "message": "Tried to reference @list_price but such attribute is not present", "transform_index": 0 } } ] } ] } ]}problem.code | Meaning |
|---|---|
mapping_reference_missing | A transform argument references a field the record does not have |
mapping_transform_failed | A transform failed on the value, such as "inf" | to_i |
mapping_target_conflict | The value cannot be merged into the field already there, such as text into an object |
A mapping that cannot run
Section titled “A mapping that cannot run”A rule that cannot apply to any record is a 422 before the sample is touched. Creating and
updating a mapping refuse the same rules the same way. Every problem is listed at once:
{ "reason": "invalid mapping rule for 'price_with_vat': unknown transform 'to_float' (and 1 more)", "exception_details": { "mapping_problems": [ { "key": "price_with_vat", "target_specification": "price | to_float", "code": "mapping_transform_unknown", "message": "unknown transform 'to_float'" }, { "key": "stock_status", "target_specification": null, "code": "mapping_target_invalid", "message": "target must be a string, not int" } ] }, "request_id": "01JQ8H3Z9K2F7M4N6P8R0S2T4V"}code | Meaning |
|---|---|
mapping_rule_invalid | The target spec does not parse, or @defaults is not an object |
mapping_target_invalid | The target is not a string |
mapping_transform_unknown | The target spec names a transform that does not exist |
A stored mapping can still name a transform that does not exist. Ingest then
rejects every record that reaches that rule with mapping_failed (see
What gets rejected). Updating the
mapping reports the rule as mapping_transform_unknown.
A sample, or values mapped from it, nested too deeply to preview is also a 422; its body carries a reason and no mapping_problems.
Managing mappings
Section titled “Managing mappings”| Operation | Endpoint |
|---|---|
| List | GET /catalog/v1/{catalog_id}/mappings |
| Create | POST /catalog/v1/{catalog_id}/mappings |
| Read | GET /catalog/v1/{catalog_id}/mappings/{mapping_id} |
| Update | PATCH /catalog/v1/{catalog_id}/mappings/{mapping_id} |
| Delete | DELETE /catalog/v1/{catalog_id}/mappings/{mapping_id} |
A mapping belongs to a catalog and an object type (catalog_type), and a feed points at
one by mapping_id. See Feed management API.
Changing a mapping affects records processed after the change. To re-apply it to your whole catalog, reset the feed’s fingerprints and resynchronize — see Forcing a full resend.
See also
Section titled “See also”- Catalog object model — what you are mapping to
- Feeds overview — source fields and catalog fields
- Feed management API — attaching a mapping to a feed
- Catalog metadata — checking the types you produced
- Feed troubleshooting
Was this page helpful?
Thanks.