query
npx skills add https://github.com/crystallizeapi/ai --skill queryCrystallize Query Skill
Query product data, content, and commerce information from Crystallize using GraphQL APIs. This skill covers reading data for storefronts, admin interfaces, product pages, search, and cart operations.
Consultation Approach
Before writing queries, understand the context. Ask clarifying questions:
- What data do you need? Products, orders, customers, content, shapes?
- Is this for a storefront or admin interface? Discovery/Catalogue for storefronts, Core for admin.
- Do you need search/filtering or exact path reads? Discovery for search and faceted navigation, Catalogue for deterministic reads by path.
- Do you have authentication configured? Core API requires access tokens. Discovery/Catalogue can be open but should be secured in production.
- What volume of results? Pagination strategy matters — cursor-based is recommended for all APIs.
Choosing the Right API
- Need search, filtering, or faceting? → Discovery API
- Know the exact path? Need strong consistency? → Catalogue API
- Admin interface? Orders, customers, shapes? → Core API
- Cart/checkout operations? → Shop API
How It Works
Crystallize provides four main APIs for querying data:
- Core API - Full read/write API with advanced filtering. Best for admin interfaces, customer/order management.
- Discovery API - Semantic API for search, browse, filter, and faceting. Best for storefronts.
- Catalogue API - Path-based reads for deterministic data access.
- Shop API - Cart and checkout queries at the edge.
API Endpoints
| API | Endpoint | Auth Required | Use For |
|---|---|---|---|
| Core | https://api.crystallize.com/@{tenant} | Yes (access tokens) | Admin, customers, orders |
| Discovery | https://api.crystallize.com/{tenant}/discovery | Optional (configurable) | Storefront search/filter |
| Catalogue | https://api.crystallize.com/{tenant}/catalogue | Optional (configurable) | Storefront path-based reads |
| Shop /cart | https://shop-api.crystallize.com/{tenant}/cart | Yes (JWT) | Cart hydration, checkout flows |
| Shop /order | https://shop-api.crystallize.com/{tenant}/order | Yes (JWT) | Order queries by ID/customer |
Usage
Core API (Admin & Advanced Queries)
The Core API provides full read/write access with advanced filtering capabilities:
- Read items by ID with full component data
- List items with pagination and filtering
- Query customers and orders with complex filters
- Filter orders by customer, SKU, payment provider, metadata
- Access shape and piece definitions
- Manage flows and archives
# Example: Get item with componentsquery GetItem { item(id: "item-id", language: "en") { ... on Product { id name components { componentId content } defaultVariant { sku price stock } } }}
# Example: List orders filtered by customerquery ListOrders { orders( tenantId: "tenant-id" first: 20 filter: { customerIdentifier: "customer@example.com" } sort: { field: createdAt, direction: desc } ) { edges { node { id total { gross currency } customer { identifier } } } }}See Core API Queries Reference for complete documentation.
Discovery API (Recommended for Storefronts)
The Discovery API is the primary API for frontend development. It supports:
- Full-text search
- Filtering by any component or attribute
- Faceted navigation
- Sorting and cursor-based pagination
The Discovery API has two entry points: search for full-text queries with facets, and browse for shape-typed access where each shape becomes its own query type.
Note: The Discovery API uses lowercase type names in inline fragments (
... on product,... on category) because types are derived from your shape identifiers. You can still use it for interface (... on Product,... on Folder).
# Example: Browse products by shape with pagination{ browse { product(language: en, pagination: { limit: 25 }) { summary { totalHits hasMoreHits endCursor } hits { name path ... on Product { defaultVariant { sku defaultPrice firstImage { url } } } } } }}
# Example: Search with facets and filtering{ search( language: en term: "green" filters: { type_in: [product] } facets: { shape: { limit: 5 } } pagination: { limit: 20 } ) { summary { totalHits hasMoreHits endCursor facets } hits { name path ... on product { defaultVariant { defaultPrice } } } }}Catalogue API (Path-Based Reads)
Use for deterministic reads when you know the exact path:
{ catalogue(language: "en", path: "/shop/plants") { name ... on Product { variants { sku name price } } }}Shop API Queries
Retrieve cart and order intent data:
query { cart(id: "cart-id") { id items { sku name quantity price { gross net } } total { gross net } }}Best Practices
- Choose the right API - Core for admin, Discovery for storefront search, Catalogue for path-based reads
- Use Discovery API for storefronts - It’s optimized for performance and supports search/filter
- Include deterministic sorting - Always add a secondary sort field (like itemId) for stable pagination
- Request only needed fields - GraphQL allows precise field selection to minimize payload
- Handle async updates - Discovery API may have sub-second delay for recently published content
- Protect APIs in production - Configure authentication for sensitive data
- Use Core API for complex filters - Only Core API supports filtering orders by customer, SKU, payment provider
References
- Core API Queries Reference - Items, customers, orders, shapes with advanced filtering
- Discovery API Reference - Detailed search, filter, and faceting documentation
- Catalogue API Reference - Path-based query documentation
- Shop API Queries Reference - Cart and checkout query documentation (
/cartendpoint) - Shop API Order Queries Reference - Order queries by ID or customer (
/orderendpoint)
Reference Details
Catalogue API Reference
The Catalogue API provides path-based access to items in your Crystallize tenant. It offers strong consistency for deterministic reads.
Base URL
https://api.crystallize.com/{tenant-identifier}/catalogueReplace {tenant-identifier} with your tenant name.
Authentication
By default, the API is accessible without authentication. You can configure access in the Crystallize App:
- Go to Settings → API Access
- Choose your preferred access level
- Save changes
When to Use Catalogue API
Use the Catalogue API when you need:
- Deterministic exports - Payload must reflect exact current state
- Path-based reads - You know the exact path to the item
- Strong consistency - Cannot tolerate async delays
For search, filtering, and faceting, use the Discovery API instead.
Query Structure
Basic Path Query
{ catalogue(language: "en", path: "/shop/plants") { name path type }}Product Query with Variants
{ catalogue(language: "en", path: "/shop/furniture/dining-chair") { name ... on Product { variants { sku name price stock attributes { attribute value } } } }}Folder with Children
{ catalogue(language: "en", path: "/shop/plants") { name ... on Folder { children { name path type } } }}Document Query
{ catalogue(language: "en", path: "/blog/welcome-post") { name ... on Document { components { id name content { ... on RichTextContent { html } } } } }}cURL Example
curl \ -X POST \ -H "Content-Type: application/json" \ --data '{ "query": "{ catalogue(language: \"en\", path: \"/shop/plants\") { name } }" }' \ https://api.crystallize.com/furniture/catalogueLimitations
The Catalogue API does not support:
- Full-text search
- Filtering
- Faceting
- Sorting across multiple items
Use the Discovery API for these capabilities.
Related Links
Core API Queries Reference
The Core API provides comprehensive read access to items, customers, orders, shapes, and tenant configuration. Use this for admin interfaces, reporting, and complex filtering needs.
Table of Contents
- Base URL
- Authentication
- Core API vs Other APIs
- Item Queries — Get by ID, list with pagination, get components, check identifiers
- Customer Queries — Get by identifier, list, customer groups
- Order Queries — Get by ID, list with advanced filtering
- Shape and Piece Queries — Shape definitions, pieces
- Flow Queries — Flows and flow content
- Archive Queries — Archived versions
- Bulk Task Queries — Task status and listing
- File and Image Queries — Images
- Error Handling — Union return types
- TypeScript Types
Base URL
https://api.crystallize.com/@{tenant-identifier}Note the @ prefix before the tenant identifier.
Authentication
Authentication is required. Use access tokens generated in the Crystallize App:
curl -X POST 'https://api.crystallize.com/@your-tenant' \ -H 'Content-Type: application/json' \ -H 'X-Crystallize-Access-Token-Id: YOUR_TOKEN_ID' \ -H 'X-Crystallize-Access-Token-Secret: YOUR_TOKEN_SECRET' \ -d '{"query": "..."}'Core API vs Other APIs
Use Core API for:
- Admin interfaces and dashboards
- Complex order filtering (by customer, SKU, payment provider)
- Reading shape/piece definitions
- Customer management
- Bulk data access
Use Discovery/Catalogue API for:
- Storefront product listings
- Search and filtering
- Public content access
Item Queries
Get Item by ID
query GetItem { item(id: "item-id", language: "en") { ... on Product { id name shapeIdentifier tree { path parentId } components { componentId content } defaultVariant { sku name price stock images { url altText } } variants { sku name price stock } } ... on Document { id name shapeIdentifier components { componentId content } } ... on Folder { id name shapeIdentifier children { id name type } } ... on ItemNotFoundError { errorName message } }}List Items with Pagination
query ListItems { items( language: "en" first: 20 after: "cursor" filter: { shapeIdentifiers: ["product", "bundle"] # path: { prefix: "/shop" } # includeDescendants: true } sort: { field: updatedAt, direction: desc } ) { edges { cursor node { id name type shapeIdentifier createdAt updatedAt ... on Product { defaultVariant { sku price stock } } } } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } totalCount }}Get Item Component
Read a specific component from an item:
query GetItemComponent { item(id: "item-id", language: "en") { ... on Item { id component(id: "description") { componentId content } } }}Check Identifier Availability
query CheckIdentifier { isIdentifierAvailable(tenantId: "tenant-id", identifier: "new-product-slug") { ... on IdentifierAvailability { available } }}Customer Queries
Get Customer by Identifier
query GetCustomer { customer(identifier: "customer@example.com") { ... on Customer { id identifier tenantId ... on IndividualCustomer { firstName lastName email phone } ... on OrganizationCustomer { name taxId organizationNumber } addresses { type firstName lastName street street2 city state postalCode country phone email } meta { key value } } ... on CustomerNotFoundError { errorName message } }}List Customers
query ListCustomers { customers( tenantId: "tenant-id" first: 20 filter: { email: { contains: "@example.com" } # customerType: individual } ) { edges { node { id identifier ... on IndividualCustomer { firstName lastName email } ... on OrganizationCustomer { name } } } pageInfo { hasNextPage endCursor } totalCount }}Get Customer Groups
query GetCustomerGroup { customerGroup(id: "group-id") { ... on CustomerGroup { id name customerIdentifiers } }}
query ListCustomerGroups { customerGroups(tenantId: "tenant-id", first: 20) { edges { node { id name customerIdentifiers } } }}Order Queries
Get Order by ID
query GetOrder { order(id: "order-id") { ... on Order { id createdAt updatedAt customer { identifier ... on IndividualCustomer { firstName lastName email } } cart { sku name quantity price { net gross currency } imageUrl } total { net gross currency } payment { provider ... on StripePayment { paymentIntentId } } meta { key value } } ... on OrderNotFoundError { message } }}List Orders with Filtering
The Core API supports advanced order filtering by customer, SKU, payment provider, and metadata:
query ListOrders { orders( tenantId: "tenant-id" first: 20 filter: { customerIdentifier: "customer@example.com" # sku: "PROD-123" # paymentProvider: "stripe" # meta: [{ key: "source", value: "mobile-app" }] } sort: { field: createdAt, direction: desc } ) { edges { cursor node { id createdAt total { gross currency } customer { identifier } cart { sku name quantity price { gross } } } } pageInfo { hasNextPage endCursor } totalCount }}Filter Options:
customerIdentifier- Filter by customer email/identifiersku- Filter orders containing specific SKUpaymentProvider- Filter by payment method (stripe, klarna, etc.)meta- Filter by metadata key/value pairscreatedAt- Date range filtering
Shape and Piece Queries
Get Shape Definition
query GetShape { shape(identifier: "product") { ... on Shape { identifier name type components { id name type config } variantComponents { id name type config } } }}List All Shapes
query ListShapes { shapes(tenantId: "tenant-id") { identifier name type itemCount }}Get Piece Definition
query GetPiece { piece(identifier: "seo") { ... on Piece { identifier name components { id name type config } } }}List All Pieces
query ListPieces { pieces(tenantId: "tenant-id", first: 100) { edges { node { identifier name components { id name type } } } }}Flow Queries
Get Flow
query GetFlow { flow(id: "flow-id") { ... on Flow { id name stages { id name position } } }}Get Flow Content
List items in a flow stage:
query GetFlowContent { flowContent(flowId: "flow-id", stageId: "stage-id", language: "en", first: 20) { edges { node { item { id name type } assignedTo { id email } dueDate } } }}Archive Queries
Get Archived Version
query GetArchive { archive(id: "archive-id") { ... on ArchivedItemVersion { id number name archivedAt archivedBy { email } item { id name } } }}List Archived Versions
query ListArchives { archives(itemId: "item-id", language: "en", first: 10, sort: { field: number, direction: desc }) { edges { node { id number name archivedAt archivedBy { email } } } }}Bulk Task Queries
Get Bulk Task Status
query GetBulkTask { bulkTask(id: "task-id") { ... on BulkTask { id type status createdAt startedAt stoppedAt info actor { email } } }}List Bulk Tasks
query ListBulkTasks { bulkTasks(tenantId: "tenant-id", first: 20, filter: { status: running }) { edges { node { id type status createdAt } } }}File and Image Queries
Get Image
query GetImage { image(key: "image-key") { ... on Image { key url altText caption variants { url width height } } }}List Images
query ListImages { images(tenantId: "tenant-id", first: 20, filter: { path: { prefix: "/products/" } }) { edges { node { key url altText createdAt } } }}Error Handling
The Core API uses union return types. Always handle potential errors:
query { item(id: "item-id", language: "en") { ... on Product { id name } ... on ItemNotFoundError { errorName message } ... on UnauthorizedError { errorName message } }}Common error types:
UnauthorizedError- Invalid or missing credentialsItemNotFoundError- Item doesn’t existCustomerNotFoundError- Customer doesn’t existOrderNotFoundError- Order doesn’t existBasicError- Generic error
TypeScript Types
Use generated types for type safety:
import { Query, GetItemQuery, GetCustomerQuery, ListOrdersQuery } from "@/generated/core";Types are generated from the Core API schema via GraphQL Code Generator.
Related Links
Discovery API Reference
The Discovery API is the primary API for powering storefronts with product information and marketing content. It is a read-only API optimized for high performance.
Base URL
https://api.crystallize.com/{tenant-identifier}/discoveryReplace {tenant-identifier} with your tenant name.
Authentication
By default, the Discovery API is open. If you configure restricted access for your Catalogue API, you need to provide authentication:
- Static token via header
- Access tokens for programmatic access
Important: Always secure your API with authentication in production environments.
Key Features
Semantic Schema
The Discovery API follows the structure of your shapes and components. Field names in queries match your shape definitions, making the API intuitive to use.
Combined Browse and Search
One query model for both browsing categories and searching products. This simplifies frontend development.
Filtering and Faceting
Filter by any attribute, component, or price range. Get facet counts for building filter UIs.
Query Structure
Basic Search
{ search(language: en, filters: { type_in: [product] }, pagination: { limit: 20, after: "XXXX" }) { summary { totalHits facets endCursor: endToken } hits { id name path } }}Filtering by Shape
{ search(language: en, filters: { shape: { equals: "sneaker" } }) { hits { name path } }}Price Range Filter
{ search(language: en, filters: { price_sales: { range: { gte: 20, lte: 20 } } }) { hits { name path } }}Full-Text Search
{ search(language: en, term: "plop") { hits { name path } }}Browse Queries
The browse API provides shape-typed access — each shape becomes its own query type with all component fields available directly. This is the recommended approach for storefronts.
Browse by Shape
{ browse { product(language: en, pagination: { limit: 25 }) { hits { name path defaultVariant { sku defaultPrice firstImage { url } } } } }}Browse by Path (Folder Listing)
{ browse { category(language: en, path: "/shop/*") { hits { name path children(language: en) { hits { ... on product { name path defaultVariant { sku defaultPrice } } } } } } }}- Use
path: "/shop/*"for direct children (wildcard) - Use
path: "/shop/exact-item"for a specific item - Use aliases to combine multiple browse queries in one request
Combined Query with Aliases
{ folder: browse { category(language: en, path: "/shop") { hits { name path } } } products: browse { product(language: en, path: "/shop/*") { hits { name path defaultVariant { sku defaultPrice } } } }}Pagination
Cursor-Based Pagination (Recommended)
Use paginationToken (returned as endCursor in summary) for efficient, consistent pagination:
{ browse { product(language: en, pagination: { limit: 25, after: "CURSOR_FROM_PREVIOUS_PAGE" }) { summary { totalHits hasMoreHits endCursor } hits { name path } } }}Flow:
- First request: omit
after(or set tonull) - Use
summary.endCursoras theaftervalue for the next page - Stop when
summary.hasMoreHitsisfalse
Note:
skip-based pagination is deprecated. Use cursor-based pagination (after) for all new implementations.skipbecomes increasingly expensive on large result sets.
The same cursor pattern works with search:
{ search(language: en, pagination: { limit: 20, after: "CURSOR" }) { summary { totalHits hasMoreHits endCursor } hits { name path } }}Faceting
Get counts for filter values:
{ search(language: en, term: "blue", facets: { shape: { limit: 5 } }) { summary { facets } hits { name path } }}Async Updates
The Discovery API is asynchronously updated from your published data and therefore eventually consistent:
- Typical delay: under 1 second
- Large imports may take longer to surface
For cases requiring exact current state, use the Catalogue API instead.
Related Links
Shop API Order Queries Reference
The Shop API /order scope provides queries for retrieving orders. This is a separate endpoint from the /cart scope.
Base URL
https://shop-api.crystallize.com/{tenant-identifier}/orderImportant: Order queries use the
/orderendpoint, NOT/cart.
Authentication
Requires JWT token with the order scope.
Authorization: Bearer YOUR_JWT_TOKENSee the Shop API Queries Reference for token generation details.
Get a Single Order
Retrieve an order by its Shop API UUID.
query GetOrder($id: UUID!) { order(id: $id) { id coreId reference type paymentStatus createdAt updatedAt additionalInformation customer { identifier firstName lastName email phone companyName type addresses { type street city postalCode country } } items { name sku productId quantity type imageUrl price { gross net taxAmount taxPercent currency } subTotal { gross net taxAmount currency } } total { gross net taxAmount taxPercent currency discounts { amount } taxBreakdown { taxRate amount } } payments { provider transactionId amount method createdAt } pipelines { identifier stage } appliedPromotions { identifier name } meta metaProperty(key: "fulfillment_status") }}Variables:
{ "id": "order-uuid-here"}Get Orders by Customer
Retrieve orders for a specific customer with pagination.
query GetCustomerOrders($customerIdentifier: String!, $limit: Int, $skip: Int) { orders(customerIdentifier: $customerIdentifier, limit: $limit, skip: $skip) { id coreId type paymentStatus createdAt total { gross net currency } items { name sku quantity price { gross net } } pipelines { identifier stage } }}Variables:
{ "customerIdentifier": "john@example.com", "limit": 10, "skip": 0}Order Response Types
Order
| Field | Type | Description |
|---|---|---|
id | UUID! | Shop API order ID |
coreId | String | Core API order ID (for PIM operations) |
reference | String | Order reference number |
type | OrderType | standard, draft, creditNote, etc. |
additionalInformation | String | Free-text additional info |
stockLocationIdentifier | String | Stock location identifier |
relatedOrderIds | [String] | IDs of related orders |
createdAt | DateTime | Creation timestamp |
updatedAt | DateTime | Last update timestamp |
context | HashMap | Order context (pricing, language) |
customer | Customer | Customer details |
items | [Item] | Order line items |
total | TotalPrice! | Order totals |
paymentStatus | OrderPaymentStatus | paid, unpaid, refunded, etc. |
payments | [Payment] | Payment records |
pipelines | [Pipeline] | Pipeline/workflow stage assignments |
appliedPromotions | [PromotionSlim!] | Applied promotion details |
meta | HashMap | All metadata as key-value map |
metaProperty(key) | String | Single metadata value by key |
Item (Order Line Item)
| Field | Type | Description |
|---|---|---|
name | String! | Item display name |
sku | String | Product SKU |
productId | String | Crystallize product ID |
quantity | PositiveInt | Quantity ordered |
group | String | Item grouping |
type | CartItemType | standard, shipping, fee, etc. |
imageUrl | String | Item image URL |
price | ItemPrice! | Unit price |
subTotal | ItemPrice! | Line total (price × quantity) |
subscriptionContractId | String | Subscription contract reference |
subscription | Subscription | Subscription details |
meta | HashMap | Item-level metadata |
ItemPrice / TotalPrice
| Field | Type | Description |
|---|---|---|
gross | Float! | Price including tax |
net | Float! | Price excluding tax |
taxAmount | Float! | Tax amount |
taxPercent | Float! | Tax percentage |
currency | String! | Currency code |
discounts | [Discount!] | Applied discounts |
TotalPrice also includes:
| Field | Type | Description |
|---|---|---|
taxBreakdown | [TaxBreakdownEntry!] | Tax by rate |
Customer
| Field | Type | Description |
|---|---|---|
isGuest | Boolean! | Whether customer is guest |
identifier | String | Unique customer ID |
firstName | String | First name |
lastName | String | Last name |
middleName | String | Middle name |
email | String | Email address |
phone | String | Phone number |
birthDate | DateTime | Date of birth |
companyName | String | Company name |
taxNumber | String | Tax/VAT number |
type | CustomerType | individual or organization |
externalReference | String | External reference |
externalReferences | HashMap | Multiple external refs |
addresses | [Address] | Customer addresses |
meta | HashMap | Customer metadata |
Payment
| Field | Type | Description |
|---|---|---|
provider | String | Payment provider (e.g., “stripe”) |
transactionId | String | Transaction reference |
amount | Float | Payment amount |
method | String | Payment method (e.g., “card”) |
createdAt | String | Payment timestamp |
meta | HashMap | Payment metadata |
Pipeline
| Field | Type | Description |
|---|---|---|
identifier | String | Pipeline identifier |
stage | String | Current stage |
Related
- Shop API Cart Queries - Cart queries (
/cartendpoint) - Shop API Order Mutations - Creating and managing orders (
/orderendpoint)
Shop API Queries Reference
The Shop API handles cart and checkout operations at the edge for low-latency commerce experiences.
Base URL
https://shop-api.crystallize.com/{tenant-identifier}/cartThe endpoint also serves as a GraphQL playground for documentation.
Authentication
The Shop API requires a JWT token. Obtain tokens from:
POST https://shop-api.crystallize.com/{tenant-identifier}/auth/tokenGetting a Token
curl -X POST 'https://shop-api.crystallize.com/YOUR_TENANT/auth/token' \ -H 'Accept: application/json' \ -H 'x-crystallize-access-token-id: YOUR_ACCESS_TOKEN_ID' \ -H 'x-crystallize-access-token-secret: YOUR_ACCESS_TOKEN_SECRET' \ -H 'Content-Type: application/json' \ -d '{"scopes":["cart","cart:admin","order"],"expiresIn":18000}'Token Scopes
| Scope | Description |
|---|---|
cart | Manage a single cart |
cart:admin | Manage multiple carts |
order | Create and manage orders (/order endpoint) |
usage | Access usage/metrics API |
lock | Acquire/release locks |
Using the Token
Include in request headers:
Authorization: Bearer YOUR_JWT_TOKENCart Queries
Retrieve Cart
query { cart(id: "cart-id-here") { id state isStale isExpired createdAt updatedAt items { sku name quantity managed origin images { url } price { gross net taxAmount } } total { gross net taxAmount } }}Cart as Order Intent
Retrieve cart formatted for order creation:
query { cartAsOrderIntent(id: "cart-id-here") { customer { firstName lastName email } cart { sku name quantity price { gross net } } total { gross net } }}This format can be pushed directly to the Order API without transformation.
Cart Concepts
Hydration
The process where Shop API fetches and computes data on your behalf to construct or update the cart.
Cart Items
Two types of items can be in a cart:
- SKU Items - Items with a SKU that exist in Crystallize
- External Items - Items that don’t exist in Crystallize (e.g., shipping)
Managed vs Unmanaged Items
- Managed (true): Shop API fetches info from Crystallize
- Managed (false): You provide all item information
SKU items start as managed but become unmanaged if you override properties like pricing.
Expiration
- You can set cart expiration time
- Carts without updates for 3+ months are automatically deleted
Related Links
- Shop API Order Queries - Querying orders (
/orderendpoint) - Shop API Order Mutations - Creating and managing orders
- Crystallize Shop API Documentation
- Order API Documentation
Crystallize AI