One endpoint. Every call is POST to /graphql with header Content-Type: application/json and a body
{ "query": "...", "variables": { ... } }. The Run buttons below hit the live staging Odoo - edit the body and re-run.
Auth calls need Authorization: Bearer <token> - paste a token here after logging in:
Card shape: what the list, hover quick view and drawer preview need. Small, sparse. Has has_variants for the smart "+".
Param
Type
Notes
slugrequired
String
e.g. hayabusa-t3-boxing-gloves
locale
String
de / fr / it / en (default de)
productDetail query
Full PDP body: attribute_axes with display_type + html_color per value, all variants, precomputed variant_matrix (combo_key sorted alphabetically by attribute_slug), images, badges.
Param
Type
Notes
slugrequired
String
same as productCard
locale
String
de / fr / it / en
productList query
Cursor-paginated list of cards. next_cursor is opaque; pass it back as cursor for the next page; null on the last page.
Param
Type
Notes
category_slug
String
optional filter
locale
String
de / fr / it / en
limit
Int
default 24
cursor
String
from a previous response's next_cursor
Images (Phase 2b)
image pipeline how it works
One binary is stored once. The BFF mints one public URL per locale, each ending in the
translated product name, so a FR crawler sees gants-de-boxe.webp and a DE crawler
sees boxhandschuhe.webp for the same stored file. imgproxy only ever receives a key,
a width and a format, never a language.
URL shape: /img/{base64url(s3_key)}/{width}x{dpr}/{localized-name}.{format}
Rendered pixels are width * dpr. The trailing name is decorative and exists for SEO:
the key is authoritative, so renaming a product never breaks an indexed URL.
Formats: webp, avif, jpg, png.
Responses carry cache-control: immutable.
Field on Image
Type
Notes
src
String
resolved for the requested locale
src_i18n
I18n
one URL per locale, same binary
srcset
[ImageRendition]
1x / 2x / 3x, build url 1x, url 2x, url 3x
alt / alt_i18n
String / I18n
localized alt text
width / height
Int
intrinsic pixels, reserve the box so CLS stays 0
is_primary
Boolean
use this one for og:image
variant_id
Int
null = template level, else the variant it belongs to
Failures return { "errors": [...] } with extensions.code from a fixed set and a translated extensions.user_message { de, fr, it, en }. Switch on code, never on the text.
code
when
CART_STOCK_INSUFFICIENT
ordering more than available stock
CART_VARIANT_UNAVAILABLE
variant archived / inactive
AUTH_INVALID_CREDENTIALS
wrong email or password
ODOO_ACCESS_DENIED
the integration user lacks rights on a model
ODOO_UNAVAILABLE
Odoo is down or unreachable. retryable: true, so show a retry rather than a hard error