Appearance
API integration
The API integration allows vendors without a supported e-commerce platform to connect with a Garnet marketplace via a REST API. Vendors can push products, update prices and stock, submit tracking information, and receive orders via webhook.
Integration scope
- ✅ Product synchronization (push from vendor)
- ✅ Price and stock updates
- ✅ Order synchronization (webhook to vendor)
- ✅ Tracking synchronization (push from vendor)
Authentication
All API requests must include a Bearer token in the Authorization header. The API key is provided when the marketplace admin creates the integration in Garnet > Settings > Integrations > API.
Authorization: Bearer your_api_keyBase URL: https://api.garnetmarketplace.com
Quick start
Here is a full example: create a pineapple product, then update its stock.
Step 1 — Create the product:
Full curl command
bash
curl -X POST https://api.garnetmarketplace.com/products \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"externalId": "pineapple-001",
"title": "Fresh Pineapple",
"descriptionHtml": "<p>Sweet and juicy tropical pineapple, hand-picked at peak ripeness.</p>",
"productType": "Fruits",
"tags": ["tropical", "fresh", "fruit"],
"variants": [
{
"sku": "PINE-SINGLE",
"price": 4.99,
"inventoryQuantity": 120,
"weight": { "value": 900, "unit": "GRAMS" }
}
],
"images": [
{ "src": "https://images.unsplash.com/photo-1550258987-190a2d41a8ba?w=800", "alt": "Fresh pineapple" }
]
}'Response:
json
{ "productId": "gid://shopify/Product/123456789" }Step 2 — Update stock and price:
Full curl command
bash
curl -X PATCH https://api.garnetmarketplace.com/products \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '[
{ "sku": "PINE-SINGLE", "price": 3.99, "stock": 85 }
]'Response:
json
{ "updated": 1, "unknownSkus": [] }Endpoints
POST /products
Create or update a product in the marketplace. If a product with the same externalId already exists, it will be updated. On first creation, the product is created as DRAFT. The marketplace admin can then approve and publish it.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | yes | Unique product identifier in the vendor's system |
title | string | yes | Product title |
descriptionHtml | string | no | Product description in HTML |
vendor | string | no | Brand name (defaults to the vendor name) |
productType | string | no | Product type (e.g. "Bags") |
tags | string[] | no | Product tags |
productOptions | object[] | no | Product options (see below) |
variants | object[] | yes | At least one variant (see below) |
images | object[] | no | Product images (see below) |
metafields | object[] | no | Custom metafields (see below) |
Product option
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Option name (e.g. "Size") |
values | string[] | yes | Option values (e.g. ["S", "M", "L"]) |
Variant
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | yes | SKU identifier |
barcode | string | no | Barcode (EAN, UPC, etc.) |
price | number | yes | Sale price |
compareAtPrice | number | no | Original price before discount |
inventoryQuantity | integer | no | Available stock quantity |
tracked | boolean | no | Whether inventory is tracked (default: true) |
requiresShipping | boolean | no | Whether shipping is required (default: true) |
weight | object | no | { value: number, unit: "GRAMS" | "KILOGRAMS" | "OUNCES" | "POUNDS" } |
optionValues | object[] | no | [{ optionName: "Size", name: "M" }] — must match productOptions |
externalVariantId | string | no | Unique variant identifier in the vendor's system |
Image
| Field | Type | Required | Description |
|---|---|---|---|
src | string | yes | Public URL of the image |
alt | string | no | Alt text for accessibility |
externalId | string | no | Unique image ID to avoid re-uploading on updates |
Metafield
| Field | Type | Required | Description |
|---|---|---|---|
key | string | yes | Metafield key (e.g. "material") |
value | string | yes | Metafield value (e.g. "leather") |
Example request:
bash
curl -X POST https://api.garnetmarketplace.com/products \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"externalId": "vendor-product-123",
"title": "Premium Leather Bag",
"descriptionHtml": "<p>A beautiful leather bag</p>",
"productType": "Bags",
"tags": ["leather", "premium"],
"productOptions": [
{ "name": "Size", "values": ["S", "M", "L"] }
],
"variants": [
{
"sku": "BAG-001-S",
"price": 129.99,
"inventoryQuantity": 50,
"optionValues": [{ "optionName": "Size", "name": "S" }]
},
{
"sku": "BAG-001-M",
"price": 129.99,
"inventoryQuantity": 30,
"optionValues": [{ "optionName": "Size", "name": "M" }]
}
],
"images": [
{ "src": "https://example.com/bag-front.jpg", "alt": "Front view" }
],
"metafields": [
{ "key": "material", "value": "leather" },
{ "key": "country_of_origin", "value": "Italy" }
]
}'Response:
json
{ "productId": "gid://shopify/Product/123456789" }PATCH /products
Bulk update price and stock for existing products by SKU. Maximum 250 items per request. Only updates variants that match existing SKUs in the marketplace.
Request body: Array of objects:
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | yes | SKU identifier |
price | number | yes | New price |
stock | integer | yes | New stock quantity |
Example request:
bash
curl -X PATCH https://api.garnetmarketplace.com/products \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '[
{ "sku": "BAG-001-S", "price": 119.99, "stock": 45 },
{ "sku": "BAG-001-M", "price": 119.99, "stock": 28 }
]'Response:
json
{ "updated": 1, "unknownSkus": [] }POST /tracking
Submit a tracking number for an order. This will fulfill the order on the marketplace and notify the customer.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
orderId | integer | yes | Numeric Shopify order ID (received in the order webhook) |
trackingNumber | string | yes | Tracking number |
trackingCompany | string | no | Carrier name (e.g. "UPS") |
trackingUrl | string | no | Tracking URL |
Example request:
bash
curl -X POST https://api.garnetmarketplace.com/tracking \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"orderId": 5538456789012,
"trackingNumber": "1Z999AA10123456784",
"trackingCompany": "UPS",
"trackingUrl": "https://www.ups.com/track?tracknum=1Z999AA10123456784"
}'Response:
json
{ "fulfilled": true }Webhooks
When a customer places an order on the marketplace, Garnet sends it to the vendor's system as a webhook: an HTTP POST request to the vendor's registered webhook URL, with the order as JSON payload.
WARNING
Webhook registration is not self-service yet. Contact us with the URL you want orders delivered to, you will receive in return the webhook secret used to verify deliveries.
Topics
| Topic | Sent when |
|---|---|
orders/create | A customer places an order containing at least one of the vendor's products |
orders/updated | That order is later updated (payment, cancellation, refund, address change, edit) |
The order only contains the vendor's own line items: an order spanning several vendors is split, and each vendor receives their share only.
Each delivery includes the following headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Garnet-Topic | orders/create or orders/updated |
X-Garnet-Hmac-Sha256 | Base64-encoded signature of the payload (see below) |
Respond with a 2xx status code to acknowledge the delivery. Any other response is treated as a failed delivery and reported to the Garnet team.
Verifying a webhook
Before processing a delivery, verify it was sent by Garnet and not by a third party. Compute the HMAC-SHA256 digest of the raw request body using your webhook secret as the key, base64-encode it, and compare it to the X-Garnet-Hmac-Sha256 header using a constant-time comparison. If they differ, reject the request with a 401 status.
js
import crypto from 'crypto';
function verifyWebhook(rawBody, hmacHeader, secret) {
const digest = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('base64');
return crypto.timingSafeEqual(Buffer.from(digest, 'base64'), Buffer.from(hmacHeader, 'base64'));
}TIP
The digest must be computed on the raw request body, exactly as received — before any JSON parsing or re-serialization. Most frameworks need to be configured to expose the raw body (e.g. express.raw() or the verify option of express.json()).
Order payload
The payload follows the Shopify Order datatype — the exact same format Shopify uses for its own orders/create and orders/updated webhooks.
Test payload
json
{
"id": 5290791567663,
"admin_graphql_api_id": "gid://shopify/Order/5290791567663",
"app_id": 580111,
"browser_ip": "125.208.136.218",
"buyer_accepts_marketing": false,
"cancel_reason": null,
"cancelled_at": null,
"cart_token": "64e26fd0b2dde9496bac47e76abb1cff",
"checkout_id": 36641550467375,
"checkout_token": "104e23f0d8060aa91880be1d3736068c",
"client_details": {
"accept_language": "en-SG",
"browser_height": null,
"browser_ip": "125.208.136.218",
"browser_width": null,
"session_hash": null,
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/113.0"
},
"closed_at": null,
"confirmed": true,
"contact_email": "vendor+test@garnetmarketplace.com",
"created_at": "2023-04-26T05:13:22-04:00",
"currency": "SGD",
"current_subtotal_price": "126.00",
"current_subtotal_price_set": {
"shop_money": {
"amount": "126.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "126.00",
"currency_code": "SGD"
}
},
"current_total_additional_fees_set": null,
"current_total_discounts": "0.00",
"current_total_discounts_set": {
"shop_money": {
"amount": "0.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "0.00",
"currency_code": "SGD"
}
},
"current_total_duties_set": null,
"current_total_price": "143.58",
"current_total_price_set": {
"shop_money": {
"amount": "143.58",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "143.58",
"currency_code": "SGD"
}
},
"current_total_tax": "10.08",
"current_total_tax_set": {
"shop_money": {
"amount": "10.08",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "10.08",
"currency_code": "SGD"
}
},
"customer_locale": "en-SG",
"device_id": null,
"discount_codes": [],
"email": "vendor+test@garnetmarketplace.com",
"estimated_taxes": false,
"financial_status": "pending",
"fulfillment_status": null,
"landing_site": "/",
"landing_site_ref": null,
"location_id": null,
"merchant_of_record_app_id": null,
"name": "#1030",
"note": null,
"note_attributes": [],
"number": 30,
"order_number": 1030,
"order_status_url": "https://ananature.com/74172334383/orders/abec6a39605c6db59aea2a722d285b10/authenticate?key=9662ba1502890060c852ff24c798f355",
"original_total_additional_fees_set": null,
"original_total_duties_set": null,
"payment_gateway_names": ["Cash on Delivery (COD)"],
"phone": null,
"presentment_currency": "SGD",
"processed_at": "2023-04-26T05:13:20-04:00",
"reference": "305b49b833b6811a84232c0bb3340d73",
"referring_site": "",
"source_identifier": "305b49b833b6811a84232c0bb3340d73",
"source_name": "web",
"source_url": null,
"subtotal_price": "126.00",
"subtotal_price_set": {
"shop_money": {
"amount": "126.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "126.00",
"currency_code": "SGD"
}
},
"tags": "",
"tax_lines": [
{
"price": "10.08",
"rate": 0.08,
"title": "GST",
"price_set": {
"shop_money": {
"amount": "10.08",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "10.08",
"currency_code": "SGD"
}
},
"channel_liable": false
}
],
"taxes_included": false,
"test": false,
"token": "abec6a39605c6db59aea2a722d285b10",
"total_discounts": "0.00",
"total_discounts_set": {
"shop_money": {
"amount": "0.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "0.00",
"currency_code": "SGD"
}
},
"total_line_items_price": "126.00",
"total_line_items_price_set": {
"shop_money": {
"amount": "126.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "126.00",
"currency_code": "SGD"
}
},
"total_outstanding": "143.58",
"total_price": "143.58",
"total_price_set": {
"shop_money": {
"amount": "143.58",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "143.58",
"currency_code": "SGD"
}
},
"total_shipping_price_set": {
"shop_money": {
"amount": "7.50",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "7.50",
"currency_code": "SGD"
}
},
"total_tax": "10.08",
"total_tax_set": {
"shop_money": {
"amount": "10.08",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "10.08",
"currency_code": "SGD"
}
},
"total_tip_received": "0.00",
"total_weight": 0,
"updated_at": "2023-04-26T05:13:23-04:00",
"user_id": null,
"billing_address": {
"first_name": "Frncois",
"address1": "Tekka Centre",
"phone": null,
"city": "Singapore",
"zip": "123212",
"province": null,
"country": "Singapore",
"last_name": "Rulliere",
"address2": null,
"company": null,
"latitude": null,
"longitude": null,
"name": "Frncois Rulliere",
"country_code": "SG",
"province_code": null
},
"customer": {
"id": 7012590813487,
"email": "vendor+test@garnetmarketplace.com",
"accepts_marketing": false,
"created_at": "2023-04-26T05:13:21-04:00",
"updated_at": "2023-04-26T05:13:22-04:00",
"first_name": "Frncois",
"last_name": "Rulliere",
"state": "disabled",
"note": null,
"verified_email": true,
"multipass_identifier": null,
"tax_exempt": false,
"phone": null,
"email_marketing_consent": {
"state": "not_subscribed",
"opt_in_level": "single_opt_in",
"consent_updated_at": null
},
"sms_marketing_consent": null,
"tags": "",
"currency": "SGD",
"accepts_marketing_updated_at": "2023-04-26T05:13:21-04:00",
"marketing_opt_in_level": null,
"tax_exemptions": [],
"admin_graphql_api_id": "gid://shopify/Customer/7012590813487",
"default_address": {
"id": 9225310241071,
"customer_id": 7012590813487,
"first_name": "Frncois",
"last_name": "Rulliere",
"company": null,
"address1": "Tekka Centre",
"address2": null,
"city": "Singapore",
"province": null,
"country": "Singapore",
"zip": "123212",
"phone": null,
"name": "Frncois Rulliere",
"province_code": null,
"country_code": "SG",
"country_name": "Singapore",
"default": true
}
},
"discount_applications": [],
"fulfillments": [],
"line_items": [
{
"id": 13797034197295,
"admin_graphql_api_id": "gid://shopify/LineItem/13797034197295",
"fulfillable_quantity": 2,
"fulfillment_service": "manual",
"fulfillment_status": null,
"gift_card": false,
"grams": 0,
"name": "Burmanguese Pineapple",
"price": "19.00",
"price_set": {
"shop_money": {
"amount": "19.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "19.00",
"currency_code": "SGD"
}
},
"product_exists": true,
"product_id": 8214156149039,
"properties": [],
"quantity": 2,
"requires_shipping": true,
"sku": "BURMANGUESE",
"taxable": true,
"title": "Burmanguese Pineapple",
"total_discount": "0.00",
"total_discount_set": {
"shop_money": {
"amount": "0.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "0.00",
"currency_code": "SGD"
}
},
"variant_id": 44827692302639,
"variant_inventory_management": "shopify",
"variant_title": null,
"vendor": "Sunshine Sweet Produce",
"tax_lines": [
{
"channel_liable": false,
"price": "3.04",
"price_set": {
"shop_money": {
"amount": "3.04",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "3.04",
"currency_code": "SGD"
}
},
"rate": 0.08,
"title": "GST"
}
],
"duties": [],
"discount_allocations": []
},
{
"id": 13797034230063,
"admin_graphql_api_id": "gid://shopify/LineItem/13797034230063",
"fulfillable_quantity": 2,
"fulfillment_service": "manual",
"fulfillment_status": null,
"gift_card": false,
"grams": 0,
"name": "Brecheche Pineapple",
"price": "19.00",
"price_set": {
"shop_money": {
"amount": "19.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "19.00",
"currency_code": "SGD"
}
},
"product_exists": true,
"product_id": 8214156181807,
"properties": [],
"quantity": 2,
"requires_shipping": true,
"sku": "BRCHECHE",
"taxable": true,
"title": "Brecheche Pineapple",
"total_discount": "0.00",
"total_discount_set": {
"shop_money": {
"amount": "0.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "0.00",
"currency_code": "SGD"
}
},
"variant_id": 44807633404207,
"variant_inventory_management": "shopify",
"variant_title": null,
"vendor": "Sunshine Sweet Produce",
"tax_lines": [
{
"channel_liable": false,
"price": "3.04",
"price_set": {
"shop_money": {
"amount": "3.04",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "3.04",
"currency_code": "SGD"
}
},
"rate": 0.08,
"title": "GST"
}
],
"duties": [],
"discount_allocations": []
},
{
"id": 13797034262831,
"admin_graphql_api_id": "gid://shopify/LineItem/13797034262831",
"fulfillable_quantity": 1,
"fulfillment_service": "manual",
"fulfillment_status": null,
"gift_card": false,
"grams": 0,
"name": "Blue Pineapple",
"price": "50.00",
"price_set": {
"shop_money": {
"amount": "50.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "50.00",
"currency_code": "SGD"
}
},
"product_exists": true,
"product_id": 8262501531951,
"properties": [],
"quantity": 1,
"requires_shipping": true,
"sku": "green-pineapple",
"taxable": true,
"title": "Blue Pineapple",
"total_discount": "0.00",
"total_discount_set": {
"shop_money": {
"amount": "0.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "0.00",
"currency_code": "SGD"
}
},
"variant_id": 44959491457327,
"variant_inventory_management": "shopify",
"variant_title": null,
"vendor": "Lampy Electronics Pte Ltd",
"tax_lines": [
{
"channel_liable": false,
"price": "4.00",
"price_set": {
"shop_money": {
"amount": "4.00",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "4.00",
"currency_code": "SGD"
}
},
"rate": 0.08,
"title": "GST"
}
],
"duties": [],
"discount_allocations": []
}
],
"payment_terms": null,
"refunds": [],
"shipping_address": {
"first_name": "Frncois",
"address1": "Tekka Centre",
"phone": null,
"city": "Singapore",
"zip": "123212",
"province": null,
"country": "Singapore",
"last_name": "Rulliere",
"address2": null,
"company": null,
"latitude": null,
"longitude": null,
"name": "Frncois Rulliere",
"country_code": "SG",
"province_code": null
},
"shipping_lines": [
{
"id": 4327852114223,
"carrier_identifier": "650f1a14fa979ec5c74d063e968411d4",
"code": "Expedited",
"delivery_category": null,
"discounted_price": "7.50",
"discounted_price_set": {
"shop_money": {
"amount": "7.50",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "7.50",
"currency_code": "SGD"
}
},
"phone": null,
"price": "7.50",
"price_set": {
"shop_money": {
"amount": "7.50",
"currency_code": "SGD"
},
"presentment_money": {
"amount": "7.50",
"currency_code": "SGD"
}
},
"requested_fulfillment_service_id": null,
"source": "shopify",
"title": "Expedited",
"tax_lines": [],
"discount_allocations": []
}
]
}Error responses
All endpoints return JSON errors in the following format:
json
{ "error": "Error message describing what went wrong" }| Status | Description |
|---|---|
| 400 | Invalid request (validation error) |
| 401 | Invalid or missing API key |
| 404 | Resource not found |
| 500 | Server error |