Product catalog
Create and manage the products and categories used to build itemized payment links and checkout pages
Kashier keeps a per-merchant product catalog — products and the categories that organize them — under /v2/products and /v2/category. It's the same catalog used when a payment page or payment link is built from line items instead of a single flat amount, and every stock-affecting change to a product is recorded on an append-only timeline you can inspect after the fact.
Scope of this page
This page covers the merchant-facing CRUD subset of /v2/products and /v2/category — the routes that require merchant authentication. Two /v2/category routes are intentionally left out: GET /v2/category/paymentPage/:ppLink/summary and GET /v2/category/simple are public and take no merchant credentials. The first is consumed by the checkout page itself to render a payment page's categories; the second is a read-only lookup that takes a merchantId query parameter. Neither is part of managing your catalog.
Product fields
These fields come from the verified products-and-services-management data model. The exact wire format for a given response isn't independently confirmed on every route below — where that matters, the "Try it" panel is the fastest way to check.
| Field | Type | Settable via API | Notes |
|---|---|---|---|
productName | string | yes | Part of a per-merchant merchantId + currency + productName uniqueness constraint — you can't have two products with the same name in the same currency. |
productDescription | string | yes | Free-text description. |
productReference | string | yes | Your own SKU/reference. |
unitPrice | number | yes | Minimum 0. |
currency | string | yes | Defaults to EGP. |
quantity | number | yes | Stock quantity. Minimum 0. |
productImage | string | yes | Object key for the product image; echoed back as productImageUrl, a full static.kashier.io URL. |
isVariant | boolean | yes | Whether the product has size variants. Defaults to false. |
variantsType | string (size) | yes | Variant dimension, when isVariant is true. |
isShippable | boolean | yes | Defaults to false. |
dimensions.height / .width / .length / .weight / .notes | number / string | yes | Shipping dimensions. |
productVariants[] | array | yes | Embedded size variants, each with variantType, name, quantity, unitPrice (minimum 0.1), variantReference, status, variantDimensions. |
categories[] | array of category IDs | yes | Categories this product belongs to. |
productStatus | string (available | outOfStock) | system-managed | Derived from stock levels. |
reservedQuantity / purchasedQuantity / totalRevenue | number | system-managed | Running totals maintained by the service. |
merchantId / createdByUserId / creationDate | — | system-managed | Set from the authenticated request. |
Category fields
| Field | Type | Settable via API | Notes |
|---|---|---|---|
name | string | yes | Required. |
slug | string | system-managed | Derived from name, and unique per merchant (merchantId + slug) — two of your categories can't resolve to the same slug, so names have to differ. |
numberOfProducts | number | system-managed | Denormalized count of products tagged with this category; not a live join, so treat it as approximate. |
merchantId / createdByUserId / createdAt / updatedAt | — | system-managed |
List merchant products
Returns the merchant's products.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/ |
| LIVE-URL | https://api.kashier.io/v2/products/ |
| Method | GET |
curl --location 'https://test-api.kashier.io/v2/products/' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200 with the merchant's products — individual items follow the Product fields shape above. Query parameters (pagination, search, filters) and the exact list envelope aren't confirmed here — use the "Try it" panel above against a test account to inspect a live response.
Create product
Creates a new product in the merchant's catalog.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/ |
| LIVE-URL | https://api.kashier.io/v2/products/ |
| Method | POST |
Body parameters
See Product fields for the full settable set. productName, currency and isVariant are required by the create schema; unitPrice is required for a non-variant product. A minimal example:
curl --location 'https://test-api.kashier.io/v2/products/' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"productName": "Wireless Mouse",
"productReference": "SKU-1001",
"currency": "EGP",
"unitPrice": 350,
"quantity": 100,
"isVariant": false
}'Two uniqueness constraints to work around
productReference is optional, but it is your merchant-wide SKU and reusing one is rejected with Validation failed / SKU must be unique — the panel below leaves it out for that reason, so add your own value when you need one. productName is constrained too, on merchantId + currency + productName, so change the name in the panel before sending: running it twice with the same name and currency is rejected as a duplicate.
Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 201 with the created product, in the Product fields shape — including the system-managed fields (productStatus, merchantId, timestamps, and so on).
Get product details
Fetch a single product by ID.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/:id |
| LIVE-URL | https://api.kashier.io/v2/products/:id |
| Method | GET |
curl --location 'https://test-api.kashier.io/v2/products/:id' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200 with the product in the Product fields shape.
Update product
Update fields on an existing product.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/:id |
| LIVE-URL | https://api.kashier.io/v2/products/:id |
| Method | PUT |
Body parameters
Same settable fields as Create product — see Product fields.
curl --location --request PUT 'https://test-api.kashier.io/v2/products/:id' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"unitPrice": 375,
"quantity": 80
}'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns the updated product in the same shape as Get product details.
Get product timeline
Returns the append-only audit/inventory timeline for a single product — every stock created / added / reduced / reserved / purchased / updated event, in order.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/:id/timeline |
| LIVE-URL | https://api.kashier.io/v2/products/:id/timeline |
| Method | GET |
curl --location 'https://test-api.kashier.io/v2/products/:id/timeline' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200 with a list of timeline entries. Per the underlying schema, each entry carries operation (created | reduced | added | purchased | updated | reserved), numOfUnit (the quantity delta), actionDate, actionBy (userId, email, fullName of who made the change), and — when the event came from a sale — customPurchasedAttributes linking back to the transactionId, orderId, and payment page (ppId) that consumed the stock. The exact envelope (pagination, wrapper key) isn't confirmed here — use the "Try it" panel above against a test account.
Assign categories to product
Adds or removes categories on one or more products, in bulk.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/assign/categories |
| LIVE-URL | https://api.kashier.io/v2/products/assign/categories |
| Method | PUT |
Body parameters
| Key | Required | Description |
|---|---|---|
categoryIds | yes | Array of category IDs to apply. Get them from Get all categories. |
productIds | yes | Array of product IDs to apply them to. Get them from List merchant products. |
operation | yes | assign or unassign. |
curl --location --request PUT 'https://test-api.kashier.io/v2/products/assign/categories' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"categoryIds": ["507f1f77bcf86cd799439011"],
"productIds": ["507f1f77bcf86cd799439012"],
"operation": "assign"
}'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200. Invalid IDs are rejected with 400 {"message":"Invalid category IDs"}.
No 'Try it' panel for this one
Both IDs have to be real objects in your own catalog, and the call rewrites the categories[] of every product you name — there is no placeholder pair that produces a valid, harmless request. Run the curl above with IDs from your own account instead. The route adds to a product's existing categories[] (or removes from it, with operation: "unassign") and notifies the payment-pages service of the change; the field names above come from Kashier's request-validation schema, but confirm the behaviour on a test product before relying on it in production.
Change product categories
A second, related route for managing a product's categories, alongside Assign categories to product.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/categories/change |
| LIVE-URL | https://api.kashier.io/v2/products/categories/change |
| Method | PUT |
Body parameters
The same categoryIds and productIds arrays as Assign categories to product, both required — but no operation, which is the one structural difference between the two routes.
| Key | Required | Description |
|---|---|---|
categoryIds | yes | Array of category IDs. |
productIds | yes | Array of product IDs. |
curl --location --request PUT 'https://test-api.kashier.io/v2/products/categories/change' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"categoryIds": ["507f1f77bcf86cd799439011"],
"productIds": ["507f1f77bcf86cd799439012"]
}'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
No 'Try it' panel for this one
Same reason as Assign categories to product: the call needs real product and category IDs from your own catalog and rewrites the products it names, so there is no safe placeholder body to ship. If your integration depends on whether this route replaces a product's category set or adds to it, verify that behaviour on a test product first — the two routes are close enough in name that you should not assume.
Export merchant products
Downloads the merchant's product catalog as a file.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/export |
| LIVE-URL | https://api.kashier.io/v2/products/export |
| Method | GET |
curl --location 'https://test-api.kashier.io/v2/products/export' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--output products.xlsxHeaders
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Export merchant products asynchronously
Same export, run as a background job instead of a synchronous download — useful for large catalogs. Rather than returning the file directly, it queues the export and is intended to email a download link once the file is ready.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/export-async |
| LIVE-URL | https://api.kashier.io/v2/products/export-async |
| Method | GET |
curl --location 'https://test-api.kashier.io/v2/products/export-async' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200 acknowledging the export job was queued. The exact acknowledgment body and delivery mechanism for the finished file aren't confirmed here — use the "Try it" panel above against a test account.
Upload product file
Uploads a file associated with the merchant's catalog (for example, a product image — productImage is stored as an object key and served back via productImageUrl).
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/products/upload/ |
| LIVE-URL | https://api.kashier.io/v2/products/upload/ |
| Method | POST |
curl --location 'https://test-api.kashier.io/v2/products/upload/' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--form 'file=@"/path/to/file"'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Confirm the multipart field name before you integrate
The upload is merchant-scoped and stores the file in Kashier's object storage. The file field name above is a placeholder — confirm the multipart field name and the accepted file types against a test account before integrating.
Get all categories
Returns the merchant's product categories.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/category/ |
| LIVE-URL | https://api.kashier.io/v2/category/ |
| Method | GET |
Query parameters
sortBy is required. It is validated before authentication, so calling the route without it returns 400 {"message":"\"sortBy\" is required"} even with a valid secret key.
| Key | Required | Description |
|---|---|---|
sortBy | yes | Field to sort by, for example name or createdAt. |
sortType | no | Sort order: 1 ascending, -1 descending. |
page | no | Page number. |
limit | no | Results per page. |
q | no | Search query. |
from / to | no | Date range, DD-MM-YYYY. |
curl --location 'https://test-api.kashier.io/v2/category/?sortBy=createdAt&sortType=-1&page=1&limit=20' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200 with the merchant's categories — individual items follow the Category fields shape above, alongside pagination. Use the "Try it" panel above against a test account to see the exact list envelope.
Create product category
Creates a new category.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/category/ |
| LIVE-URL | https://api.kashier.io/v2/category/ |
| Method | POST |
Body parameters
| Key | Description |
|---|---|
| name | Category name. Required, and unique per merchant once slugified — see Category fields. Change the name in the panel below before sending, or a second run is rejected as a duplicate. |
curl --location 'https://test-api.kashier.io/v2/category/' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"name": "Electronics"
}'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 201 with the created category, in the Category fields shape — including the system-managed slug (derived from name) and numberOfProducts (starts at 0).
Get category details
Fetch a single category by ID.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/category/:id |
| LIVE-URL | https://api.kashier.io/v2/category/:id |
| Method | GET |
curl --location 'https://test-api.kashier.io/v2/category/:id' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200 with the category in the Category fields shape.
Update product category
Update a category's name.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/category/:id |
| LIVE-URL | https://api.kashier.io/v2/category/:id |
| Method | PUT |
Body parameters
| Key | Description |
|---|---|
| name | New category name. Changing it re-derives slug. |
curl --location --request PUT 'https://test-api.kashier.io/v2/category/:id' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"name": "Electronics & Accessories"
}'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns the updated category in the same shape as Get category details.
Delete product category
Delete a category.
| Endpoint | Value |
|---|---|
| TEST-URL | https://test-api.kashier.io/v2/category/:id |
| LIVE-URL | https://api.kashier.io/v2/category/:id |
| Method | DELETE |
curl --location --request DELETE 'https://test-api.kashier.io/v2/category/:id' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'Headers
| Key | Description |
|---|---|
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about Authorization. |
Response
Returns a 200 confirming deletion. The exact response body isn't confirmed here — use the "Try it" panel above against a test account.
Products deleted from a category
Deleting a category doesn't delete the products tagged with it — numberOfProducts is a denormalized counter, not an ownership relationship. Whether a deleted category's ID is also stripped from each product's categories[] is something to verify against a test account if your integration depends on it.