Build product listing
Prerequisites
- Merchant API key
- Known context for
urlorcategoryAlias(optional)
Goals
- Retrieve products with minimal fields and total count
- Fetch facets for filtering
- Support simple pagination using
skipandtake
Architecture at a glance
- Query
products→ Render list + pagination controls → Apply filters → Re‑query
APIs used
- Merchant API:
https://merchantapi.geins.io/graphql
Step-by-step
Get page info (SEO + subcategories)
Use listPageInfo to fetch basic SEO and header data for the listing context.
Request example
query listPageInfo(
$url: String!,
$channelId: String,
$languageId: String,
$marketId: String
) {
listPageInfo(
url: $url,
channelId: $channelId,
languageId: $languageId,
marketId: $marketId
) {
name
alias
canonicalUrl
meta { title description }
subCategories { name alias canonicalUrl }
}
}
{
"Accept": "application/json",
"X-ApiKey": "{MERCHANT_API_KEY}"
}
{
"url": "{LIST_PAGE_URL}",
"channelId": "{CHANNEL_ID}",
"languageId": "{LANGUAGE_ID}",
"marketId": "{MARKET_ID}"
}
curl -X POST https://merchantapi.geins.io/graphql \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-ApiKey: {MERCHANT_API_KEY}" \
-d '{"query":"query listPageInfo($url:String!,$channelId:String,$languageId:String,$marketId:String){ listPageInfo(url:$url,channelId:$channelId,languageId:$languageId,marketId:$marketId){ name alias canonicalUrl meta{ title description } subCategories{ name alias canonicalUrl } }}","variables":{"url":"{LIST_PAGE_URL}","channelId":"{CHANNEL_ID}","languageId":"{LANGUAGE_ID}","marketId":"{MARKET_ID}"}}'
Fetch products for the list (with count)
Use products with skip/take to build pagination and select minimal fields for PLP cards.
Request example
query products(
$skip: Int
$take: Int
$url: String
$filter: FilterInputType
$channelId: String
$languageId: String
$marketId: String
) {
products(
skip: $skip
take: $take
url: $url
filter: $filter
channelId: $channelId
languageId: $languageId
marketId: $marketId
) {
products {
productId
alias
name
canonicalUrl
productImages { fileName }
unitPrice { sellingPriceIncVatFormatted }
brand { name }
}
count
}
}
{
"Accept": "application/json",
"X-ApiKey": "{MERCHANT_API_KEY}"
}
{
"skip": 0,
"take": 12,
"url": "{LIST_PAGE_URL}",
"channelId": "{CHANNEL_ID}",
"languageId": "{LANGUAGE_ID}",
"marketId": "{MARKET_ID}"
}
curl -X POST https://merchantapi.geins.io/graphql \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-ApiKey: {MERCHANT_API_KEY}" \
-d '{"query":"query products($skip:Int,$take:Int,$url:String,$filter:FilterInputType,$channelId:String,$languageId:String,$marketId:String){ products(skip:$skip,take:$take,url:$url,filter:$filter,channelId:$channelId,languageId:$languageId,marketId:$marketId){ products{ productId alias name canonicalUrl productImages{ fileName } unitPrice{ sellingPriceIncVatFormatted } brand{ name } } count }}","variables":{"skip":0,"take":12,"url":"{LIST_PAGE_URL}","channelId":"{CHANNEL_ID}","languageId":"{LANGUAGE_ID}","marketId":"{MARKET_ID}"}}'
Fetch facets for filters
Retrieve facet groups via the same products operation by requesting filters. This could be done in the same query as above but is usually separated for performance reasons. Fetching of facets can often be run in the background while the initial product list is loading.
Request example
query productFilters(
$url: String
$filter: FilterInputType
$channelId: String
$languageId: String
$marketId: String
) {
products(
url: $url
filter: $filter
channelId: $channelId
languageId: $languageId
marketId: $marketId
) {
count
filters {
facets {
filterId
group
label
type
values { _id count facetId parentId label order hidden }
}
}
}
}
{
"Accept": "application/json",
"X-ApiKey": "{MERCHANT_API_KEY}"
}
{
"url": "{LIST_PAGE_URL}",
"channelId": "{CHANNEL_ID}",
"languageId": "{LANGUAGE_ID}",
"marketId": "{MARKET_ID}"
}
curl -X POST https://merchantapi.geins.io/graphql \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-ApiKey: {MERCHANT_API_KEY}" \
-d '{"query":"query productFilters($url:String,$filter:FilterInputType,$channelId:String,$languageId:String,$marketId:String){ products(url:$url,filter:$filter,channelId:$channelId,languageId:$languageId,marketId:$marketId){ count filters{ facets{ filterId group label type values{ _id count facetId parentId label order hidden } } } }}","variables":{"url":"{LIST_PAGE_URL}","channelId":"{CHANNEL_ID}","languageId":"{LANGUAGE_ID}","marketId":"{MARKET_ID}"}}'
channelId, languageId, and marketId arguments are optional and can be left out to use default values.Response example
200 OK{
"data": {
"products": {
"count": 128,
"filters": {
"facets": [
{
"filterId": "brand",
"group": null,
"label": "Brand",
"type": "Brand",
"values": [
{
"_id": "acme",
"count": 45,
"facetId": "b_acme",
"parentId": null,
"label": "Acme",
"order": 1,
"hidden": false
},
{
"_id": "techco",
"count": 32,
"facetId": "b_techco",
"parentId": null,
"label": "TechCo",
"order": 2,
"hidden": false
}
]
},
{
"filterId": "1_2",
"group": "Product Attributes",
"label": "Color",
"type": "Parameter",
"values": [
{
"_id": "p_1_2_blue",
"count": 28,
"facetId": "color",
"parentId": null,
"label": "Blue",
"order": 1,
"hidden": false
},
{
"_id": "p_1_2_red",
"count": 22,
"facetId": "color",
"parentId": null,
"label": "Red",
"order": 2,
"hidden": false
}
]
}
]
}
}
}
}
Pagination
Use skip and take parameters for pagination:
skip: Number of products to skip (default: 0, max: 6000)take: Number of products to return (default: 20, max: 200)count: Total number of matching products (use for pagination controls)
Validation
- Products array length matches
take(except when fewer remain) countreflects total items for pagination logic- Basic fields present (
name,canonicalUrl, primary image, price) - Facets returned when
filtersrequested
Options
Multi‑market support
The mutation supports optional parameters for multi-market configurations:
Authenticated access
While authentication is not required for these queries, including a JWT bearer token in the Authorization header can provide personalized results based on the authenticated user's context, for example personalized pricing and product availability.
To include authentication, add the JWT bearer token to your request headers:
"Authorization": "Bearer {JWT_TOKEN}"
Common pitfalls
- Using high
skip/takeleads to slow pages on large catalogs - Forgetting to request minimal fields can bloat payloads
- Facets update based on
filterandurlcontext — keep them in sync