Shop API
The Shop API is intended to provide a generic Service API to manage common use cases.
You don't have to use it, as you can manage the following features directly in your Service API, but the Shop API is here to remove friction and accelerate your development, hence your time to market.
The Shop API requires a valid JWT Token. The endpoint to get that token is https://shop-api.crystallize.com/your-tenant-identifier/auth/token.
The POST request must include a JSON body that contains the scopes to which you want this Token to grant access:
- cart: Give access to the Cart API part of the Shop API to manage a Cart
- cart:admin: Give access to the Cart Admin API part of the Shop API to manage many Carts
- usage: Give access to the Usage API part of the Shop API, so you can also keep track of your usage. (similar to `tenant.metrics` in the PIM API)
- lock: Give access to the Lock API part of the Shop API to acquire or release locks.
In order to issue a JWT Token, the Shop API needs a way to authenticate against the Crystallize PIM API.
curl -X POST 'https://shop-api.crystallize.com/YOUR_TENANT_IDENTIFIER/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"],"expiresIn":18000}'
That will return a JWT Token valid for 5 hours on yourtenant on the scopes: cart and cart:admin
Once you have a token, you can just pass it as a header in the request you want to perform within the Shop API. "Authorization: bearer THE_TOKEN"
Security tips
It is not recommended to fetch the token from the frontend, as credentials to the PIM API must remain secret.
The JWT Token of the Shop API does not provide any authentication or authorization on the PIM API.
You control the expiration time of the Shop API JWT Token. You should find a way in your process to rotate that token to handle revocation (if necessary).
What if my Catalogue API is not open?
If you have secured your Catalogue API, you need to provide the x-crystallize-static-auth-token or the couple x-crystallize-access-token-id/x-crystallize-access-token-secret in each subsequent request to the Shop API on top of the Shop API Token.
The Shop API Token is used to check your permissions to use the Shop API. Those additional headers will grant permissions to the Shop API to access the other APIs on the other token's behalf.
Description
When building your frontend, you're most likely going to have a Cart. In essence, to build a Cart, a list of product SKUs with the respective quantities the buyer wants is required.
But other information may also be required, such as:
- an id
- Customer information
- a Context
Based on CartInput, the Shop API will do different things to return a Cart:
- hydration: Convert the SKUs in the data, fetching the information from the Catalogue API.
- calculation: Based on the context, the correct prices will be taken and all calculations will be performed.
There's no need to manage the Cart storage, as the Shop API takes care of that for you. And it does it on the edge of the world, where the buyer is using your storefront (assuming requests to the Shop API are made from the browser).
Also, you most likely want to create an Order based on the Cart. Wouldn't it be nice if the Shop API did that for you? Well, it does!
Concepts
HydrationThis the process where the Shop API will fetch and/or compute data on your behalf to construct or update the Cart.
ExpirationIt's possible to set an expiration time for a cart and track when carts are updated. If any cart goes more than 3 months without an update, it will be deleted automatically by Crystallize.
SKU ItemThis is a concept that exists in the Input only. SKU items are items with a SKU that should exist in Crystallize.
External ItemThis is a concept that exists in the Input only. External items are items that don't exist in Crystallize, that's why you need to provide more information to the Shop API when using them.
Cart ItemThis is what composes a Cart. Hydration converts SKU items and external Items into a Cart item that you can retrieve in your queries.
OriginIn the context of a Cart Item, you check its hydration origin. crystallize or external
Managed Cart ItemAn external item will always be managed:false The Shop API does not know anything, so you are in charge of providing information about it.
A SKU item, on the other hand, is by default managed:true The Shop API knows it can get information about it in Crystallize. But it can become managed:false when you change something on it. Imagine that you have a Discount Code that changes the price of a specific managed SKU. You will want to change its price (or any other information) and you have mutations for that: setCartItem or changeCartItemPricing for instance. When performing such operations, the Shop API will opt out of management for this SKU and won't fetch information from Crystallize anymore.
Access
https://shop-api.crystallize.com/your-tenant-identifier/cart will open the GraphQL playground, and it's also the endpoint for your GraphQL queries.
The GraphQL playground serves as documentation for the API, and we've put the maximum information we could there.
Queries
There are 2 queries that you can do for now:
- Retrieve an existing Cart.
- Retrieve a Cart as an Order Intent, so you receive data already formatted and can push it to the Order API with no hassle.
Mutations
There are many mutations for managing the Cart. Let's review the main ones.
Cart HydrationHydrating the Cart means that the Shop API will get the input and fetch the information from the Catalogue API as well as performing the calculation. For instance, if you provide a CartInput with 2 or 3 SKUs:
mutation {
hydrate(input: {
items:[
{
sku: "robot-pink-standard",
quantity: 1
},
{
sku: "robot-red-standard",
taxRate: 0.05
quantity: 3
},
]
externalItems: [
{
sku:"my-shipping-cost-sku"
quantity: 1
name:"Shipping with Fedex"
images:[]
variant: {
price: {
gross: 120
net:100
}
product : {
id:"my-shipping-product-id"
path:"an url"
}
}
}
]
}) {
...dataThatYouWant
}
}
The API will return a fully hydrated Cart that contains everything you need. (If you need more, come tell us on Slack.)
Also, you will get back an ID that you can pass to the following request in order to reuse a Cart and not get a new one each time.
CartInput ContextHydrating a Cart does not follow the same logic for everyone. Your project is unique, and your rules prevail. That's why you can provide hints to the Shop API regarding calculations and localization:
- language: The Shop API fetches and returns the Product name, the Variant Name and more, which are localized. Default is "en".
- price
- taxRate: You may want to override it and use a specific one on hydration.
- decimals: Default is 0, as you should not play with Float to avoid issues with floating point numbers, but you do you.
- pricesHaveTaxesIncludedInCrystallize: Flag to indicate whether the price you have in Crystallize contains taxes.
- selectedVariantIdentifier: Might be the most important one! This is the Price Variant Identifier that you want the Shop API to use as the Price for this Cart.
- compareAtVariantIdentifier: This is optional, but is connected to the selectedVariantIdentifier. When you provide that context, the Shop API will use it to compare and calculate discounts. The most common use case here is when you have a retail price and a sale price.
- markets: If you're using price lists, this is where you would provide the markets of the buyers.
- currency: For reference and ease of displaying the Cart. It does not affect any calculations.
- discountOnNetPrices: Flag to tell the Shop API to apply the discount on the Net Prices instead of the Gross Prices.
- fallbackVariantIdentifiers: An array of price variant identifiers to fallback on in case the selectedVariantIdentifier is null.
- customerGroup: This is an optional field for providing a customer group.
- voucherCode: Another optional field to provide a voucher code.
It's really common that when the payment process is initiated in your shop that you want to lock the Cart so it is not mutable anymore. That's why you can Place the Cart to make it read-only. Behind the scenes, there's a State Machine, and the Cart has 3 simple states: Cart, Place, and Paid.
OthersThere are other mutations that enable you to change the quantity, pricing, etc. You can find details in the GraphQL playground.
You may want to list and filter all the Carts of your application to build an admin, manage abandoned carts, etc. and https://shop-api.crystallize.com/your-tenant-identifier/cart/admin is made for this.
Additionally, you can also set promotions via the /admin endpoint. Here is an example:
mutation {
setPromotions (input: [
{
identifier: "Promotions"
periods: [
{
start: "1999-02-12T20:00:00Z",
end: "2189-02-12T20:00:00Z"
}
]
triggers: {
skus: [
"smeg-robot-pink-standard",
]
}
targets: {
skus: [
"smeg-robot-red-standard"
]
}
mechanism: {
type: Percentage
value:45
}
limitations: {
repeatable: false
}
}
]){
identifier
}
}
Locking is a mechanism for managing access to shared resources in concurrent situations, or for ensuring that certain operations are atomic. In a multi-server (node) situation, you would need some way of storing the locks' states. The Shop API provides this for you.
Acquiring a lock: This operation is used to obtain a lock on a resource. If the lock is available (i.e. not held by another process), the lock store grants the lock to the requester and marks the resource as locked. If the lock is not available, the requester can either be blocked until the lock becomes available or fail immediately with an indication that the lock could not be acquired.
Release (or Unlock): This operation is used to release a previously acquired lock, making the resource available for locking by others. After a lock is released, any other process waiting for the lock can acquire it.
The lock endpoint https://shop-api.crystallize.com/your-tenant-identifier/lock gives you 2 mutations that will return either `true` or `false` based on the action. The acquire mutation also accepts a time to live (TTL) if you want to auto-release the lock, the default being 60 seconds.
For a simple example of how one can go from fetching a product to pushing an order, refer to our checkout flow tutorial.