nanocart

API Documentation

Everything you need to integrate nanocart into your site.

https://api.nanocart.io
Building with an AI coding agent? Install the official NanoCart skill — docs.nanocart.io → Build with AI — or point your tool at docs.nanocart.io/llms-full.txt.

Widget Integration

Add the nanocart widget to any page with a single script tag. No build step, no dependencies.

Script Tag

Include this once per page, replacing your-store with your store ID:

HTML
<script src="https://cdn.nanocart.io/v1/nanocart.min.js"
  data-store-id="your-store"></script>

Button Types

Use data attributes to create interactive buttons. All you need is the product slug from your admin panel.

AttributeBehaviorExample
data-nanocart-buy="slug"Adds product to cart and opens the cart drawer. Best for "Buy Now" actions.<button data-nanocart-buy="trucker-hat">Buy Now</button>
data-nanocart-add="slug"Adds product to cart and opens the cart drawer. Same as buy, use whichever label fits.<button data-nanocart-add="trucker-hat">Add to Cart</button>
data-nanocart-product="slug"Opens a product detail modal with image, variant selector, and add-to-cart.<button data-nanocart-product="trucker-hat">View Details</button>

Full Example

HTML
<!DOCTYPE html>
<html>
<body>
  <h1>My Store</h1>

  <button data-nanocart-buy="trucker-hat">Buy Now</button>
  <button data-nanocart-product="trucker-hat">View Details</button>

  <script src="https://cdn.nanocart.io/v1/nanocart.min.js"
    data-store-id="your-store"></script>
</body>
</html>

Theming

The widget is fully themeable through a stable, documented API — script-tag attributes for the common knobs, and CSS custom properties on the host element (id nanocart-widget) for complete control. Merchants can also theme with zero code from the admin portal (Settings → Widget Appearance).

HTML
<!-- Attributes: light/dark base + accent -->
<script src="https://cdn.nanocart.io/v1/nanocart.min.js"
  data-store-id="your-store"
  data-theme="light"
  data-accent-color="#EF3E32"></script>
CSS
/* Full control: set --nc-* variables on the stable host id */
#nanocart-widget {
  --nc-accent: #EF3E32;
  --nc-bg: #1d2230;
  --nc-radius: 12px;
}
Your page CSS always wins over portal settings and attributes. The complete variable list (--nc-accent, --nc-bg, --nc-surface, --nc-border, --nc-text, --nc-text-muted, --nc-text-dim, --nc-text-strong, --nc-danger, --nc-success, --nc-radius, --nc-font) with defaults is documented in the support docs theming guide.

Product Alerts Signup (Pro/Expert)

Capture emails for product updates with the <nanocart-signup> element — bare email field + button in your store color; add show-options to render the option checkboxes configured in the portal (Subscribers page). Renders nothing unless the store is Pro/Expert with Product Alerts enabled. Style via nanocart-signup::part(row|input|button|option|consent|message); override text with the label, placeholder, success-message, and error-message attributes.

HTML
<nanocart-signup show-options></nanocart-signup>

Public endpoints (no auth): GET /shop/{storeId}/alerts-config returns the form config (options, consent, accent) or {"enabled": false}; POST /shop/{storeId}/alerts-signup accepts {email, options[], consent}. Bot protection is invisible (honeypot + timing + rate limits) — no captcha needed.

JavaScript API

The widget exposes window.nanocart for programmatic control:

JS
// Add product by slug
nanocart.addBySlug('trucker-hat');

// Open product detail modal
nanocart.viewProduct('trucker-hat');

// Cart controls
nanocart.open();
nanocart.close();
nanocart.toggle();
nanocart.getItems();
nanocart.getCount();
nanocart.getSubtotal();
nanocart.clear();

Web Component: <nanocart-product>

Embed a fully self-contained product card anywhere on your page using the custom element. No JavaScript required beyond the widget script.

HTML
<nanocart-product slug="trucker-surf-hat"></nanocart-product>

The element renders inside a Shadow DOM, so its styles are fully isolated from your page CSS. It displays the product image, name, price, variant selector (including multi-axis variants), and an add-to-cart button — all synced with your store's live data.

Attributes

AttributeRequiredDescription
slugrequiredProduct slug. Must match a slug returned by the products endpoint.

Multi-axis variant support

When a product has multiple option axes (e.g. Size and Color), the component renders a separate selector for each axis. The active variant is resolved from the combination of all selected values using the optionValues map on each variant (see Get Product).

HTML
<!-- Single product card -->
<nanocart-product slug="trucker-surf-hat"></nanocart-product>

<!-- Multiple cards side by side -->
<div style="display:flex;gap:16px">
  <nanocart-product slug="trucker-surf-hat"></nanocart-product>
  <nanocart-product slug="board-shorts"></nanocart-product>
</div>

Authentication

Public endpoints (products, categories, checkout) require no authentication. They're meant to be called from your storefront.

Admin endpoints require an API key passed in the x-api-key header. You can find your API key in the admin panel under Settings.

bash
curl https://api.nanocart.io/shop/my-store/admin/products \
  -H "x-api-key: sc_live_your_api_key_here"
API Key Format: sc_live_ followed by 64 hex characters. Keep your API key secret — it provides full admin access to your store.

Conventions

Prices

All monetary values are in cents (integers). Divide by 100 for display.

API ValueDisplay
999$9.99
2800$28.00
0$0.00 (free)
Multi-currency: Each store has a single currency field (e.g. usd, eur, gbp). All price values in the API are in that currency's minor unit (cents for USD, pence for GBP, etc.). The currency is returned in the storefront config and store object so you can format amounts correctly for your locale.

Pagination

List endpoints return a lastKey field. If non-null, pass it as a query parameter to get the next page:

GET /shop/my-store/products?limit=20&lastKey=eyJzdG9yZUlkIjoi...

Dates

All timestamps are ISO 8601 UTC strings: 2026-02-24T15:30:00Z

Response Format

All responses return JSON with appropriate HTTP status codes. Error responses include an error field and optional code field.

Service Tiers

Each store has a tier that determines limits. Upgrade from the admin panel or via the subscription API.

LimitFreeStandardProExpert
Price$0$5/mo$10/mo$25/mo
Active Products225100Unlimited
Monthly Orders55005,000Unlimited
Coupons1510Unlimited
File Upload5 MB25 MB50 MB500 MB
Shipping MethodsFlat rate, FreeAllAllAll
Local PickupNoYesYesYes

Public API

These endpoints require no authentication. Call them from your storefront to display products and process orders.

GET /shop/{storeId}/products
List active products with optional filtering, sorting, and pagination.

Query Parameters

ParameterTypeDefaultDescription
categorystringFilter by category slug
sortstringnewestnewest, price, or featured
limitinteger50Max results per page (max 50)
lastKeystringPagination token from previous response

Response

JSON
{
  "products": [
    {
      "productId": "dd9392ed-ea5a-494c-adbc-a8ce5288c2e8",
      "slug": "trucker-surf-hat",
      "name": "Trucker Surf Hat",
      "price": 2800,
      "compareAtPrice": null,
      "images": ["https://images.unsplash.com/photo-..."],
      "variants": [
        {
          "variantId": "d90da05a",
          "name": "Adjustable / Navy",
          "price": 2800,
          "sku": "TSH-ADJ-NVY",
          "optionValues": { "Size": "Adjustable", "Color": "Navy" }
        }
      ],
      "featured": false,
      "categoryId": "abc-123",
      "inventory": 50,
      "productType": "physical",
      "status": "active",
      "createdAt": "2026-02-23T20:59:28Z",
      "updatedAt": "2026-02-23T20:59:28Z"
    }
  ],
  "lastKey": null
}
GET /shop/{storeId}/products/{slug}
Get full details for a single product by slug.

Response

JSON
{
  "product": {
    "productId": "dd9392ed-ea5a-494c-adbc-a8ce5288c2e8",
    "slug": "trucker-surf-hat",
    "name": "Trucker Surf Hat",
    "description": "Classic trucker hat with embroidered surf logo.",
    "price": 2800,
    "compareAtPrice": null,
    "categoryId": "abc-123",
    "images": ["https://images.unsplash.com/photo-..."],
    "variants": [
      {
        "variantId": "d90da05a",
        "name": "Adjustable / Navy",
        "price": 2800,
        "sku": "TSH-ADJ-NVY",
        "inventory": 25,
        "optionValues": { "Size": "Adjustable", "Color": "Navy" }
      }
    ],
    "options": [
      { "name": "Size", "values": ["Adjustable"] },
      { "name": "Color", "values": ["Navy", "Black"] }
    ],
    "inventory": 50,
    "productType": "physical",
    "shippingRequired": true,
    "taxable": true,
    "tags": ["hats", "surf"],
    "featured": false,
    "status": "active",
    "createdAt": "2026-02-23T20:59:28Z",
    "updatedAt": "2026-02-23T20:59:28Z"
  }
}

Errors

StatusResponse
404{"error": "Product not found"}
GET /shop/{storeId}/categories
List all active categories for a store, sorted by sortOrder. Categories support a parent/child hierarchy via the parentId field. Top-level categories have an empty parentId. Subcategories reference their parent's categoryId.

Response

JSON
{
  "categories": [
    {
      "categoryId": "cat-001",
      "name": "Men's",
      "slug": "mens",
      "description": "Men's clothing and accessories",
      "image": "https://...",
      "parentId": "",
      "sortOrder": 0,
      "status": "active"
    },
    {
      "categoryId": "cat-002",
      "name": "Shirts",
      "slug": "shirts",
      "description": "Men's shirts",
      "image": "https://...",
      "parentId": "cat-001",
      "sortOrder": 0,
      "status": "active"
    },
    {
      "categoryId": "cat-003",
      "name": "Pants",
      "slug": "pants",
      "description": "",
      "image": "",
      "parentId": "cat-001",
      "sortOrder": 1,
      "status": "active"
    }
  ]
}

Category Fields

FieldTypeDescription
categoryIdstringUnique category identifier
namestringCategory display name
slugstringURL-friendly name (unique within store)
descriptionstringOptional description
imagestringOptional image URL (for category cards)
parentIdstringEmpty string for top-level categories. Set to a parent categoryId to make this a subcategory.
sortOrdernumberDisplay order (lower = first)
statusstringactive or hidden
Subcategory pattern: To build a hierarchical category tree, filter categories where parentId is empty to get top-level parents, then filter where parentId === parent.categoryId to get children. Products are assigned to a single categoryId — when displaying a parent category, you may want to also include products from its subcategories.
POST /shop/{storeId}/coupons/validate
Validate a coupon code and calculate the discount amount.

Request Body

JSON
{
  "code": "SAVE20",
  "subtotal": 4999
}

Response (valid)

JSON
{
  "valid": true,
  "type": "percent_off",
  "value": 20,
  "discountAmount": 999,
  "message": "20% off applied!"
}

Response (invalid)

JSON
{
  "valid": false,
  "message": "This coupon has expired"
}
Coupon Types: percent_off, fixed_amount, free_shipping
GET /shop/{storeId}/payment-methods
Returns which payment processors are configured for the store. Use this to decide which checkout buttons to show — only display buttons for processors that are actually enabled.

Response

JSON
{
  "stripe": true,
  "paypal": false
}

Both fields are booleans. A value of false means that processor is not configured for the store — do not show its checkout button. If both are false, show a "payments not configured" message rather than a dead button.

The nanocart widget calls this endpoint automatically on first cart open and caches the result for the page session. If you are building a custom checkout UI, fetch this endpoint once and render buttons accordingly.
POST /shop/{storeId}/checkout
Create a checkout session. Validates inventory, applies coupons, calculates shipping and tax, and returns a payment URL to redirect the customer to. Supports Stripe and PayPal.

Request Body

JSON
{
  "items": [
    {
      "productId": "dd9392ed-ea5a-494c-adbc-a8ce5288c2e8",
      "variantId": "d90da05a",
      "quantity": 2
    }
  ],
  "email": "customer@example.com",
  "couponCode": "SAVE20",
  "shippingMethod": "standard",
  "processor": "stripe"
}
FieldTypeRequiredDescription
itemsarrayrequiredCart items with productId, optional variantId, and quantity
emailstringrequiredCustomer email for order confirmation
couponCodestringoptionalCoupon code to apply
shippingMethodstringoptionalstandard (default) or local_pickup
processorstringoptionalstripe (default) or paypal — must match a processor configured for the store

Response

JSON
{
  "sessionUrl": "https://www.sandbox.paypal.com/checkoutnow?token=...",
  "orderId": "a1b2c3d4-e5f6-..."
}

Redirect the customer to sessionUrl to complete payment. For Stripe this is a checkout.stripe.com URL; for PayPal it is a paypal.com approval URL. The redirect and return flow is identical from your code's perspective regardless of processor.

Errors

StatusError
400Cart is empty
400Email address is required for checkout.
400Product {id} is not available
400{item} is out of stock
400PayPal is not configured for this store
403This store has reached its monthly order limit
Redirect URLs: After payment, the customer is redirected back to your site using the Referer or Origin header. Store the orderId in localStorage before redirecting so you can look up the order on your success page. This works the same for both Stripe and PayPal.
GET /shop/{storeId}/orders/{orderId}?email=customer@example.com
Look up an order by ID. Requires the customer's email for verification.

Query Parameters

ParameterTypeRequiredDescription
emailstringrequiredMust match the order's email

Response

JSON
{
  "order": {
    "orderId": "a1b2c3d4-e5f6-...",
    "orderNumber": "MYS-1001",
    "email": "customer@example.com",
    "customerName": "John Doe",
    "items": [
      {
        "productId": "dd9392ed-...",
        "name": "Trucker Surf Hat",
        "variantName": "Adjustable / Navy",
        "price": 2800,
        "quantity": 2
      }
    ],
    "subtotal": 5600,
    "shippingCost": 499,
    "shippingMethod": "standard",
    "taxAmount": 336,
    "discountAmount": 0,
    "total": 6435,
    "shippingAddress": {
      "name": "John Doe",
      "line1": "123 Main St",
      "city": "Charleston",
      "state": "SC",
      "zip": "29401",
      "country": "US"
    },
    "status": "paid",
    "createdAt": "2026-02-24T12:00:00Z"
  }
}
GET /shop/{storeId}/storefront-config
Get the storefront configuration for a hosted store. Used by the storefront SPA on page load to get template, branding, and content settings. Only works for stores with an active hosted plan.

Response

JSON
{
  "storeId": "my-store",
  "storeName": "My Store",
  "currency": "usd",
  "template": "classic",
  "accentColor": "#418a9e",
  "backgroundColor": "#FFFFFF",
  "textColor": "#333333",
  "storeDescription": "Your store description here",
  "aboutContent": "About page content...",
  "aboutTitle": "About My Store",
  "aboutImage": "https://...",
  "heroTitle": "Welcome to My Store",
  "heroLayout": "center",
  "heroImage": "",
  "logo": "https://...",
  "showStoreName": true,
  "favicon": "",
  "announcementBar": {
    "enabled": false,
    "text": "",
    "backgroundColor": "#418a9e",
    "textColor": "#FFFFFF"
  },
  "socialLinks": {
    "instagram": "",
    "twitter": "",
    "facebook": "",
    "tiktok": "",
    "youtube": ""
  },
  "showPoweredBy": true
}
GET /shop/resolve-domain?domain={domain}
Look up a custom domain to find the associated store. Used by the hosted storefront SPA when running on a custom domain instead of a *.nanocart.io subdomain.

Query Parameters

ParameterTypeRequiredDescription
domainstringrequiredThe custom domain to resolve (e.g. shop.mybrand.com)

Response

JSON
{
  "storeId": "my-store"
}

Errors

StatusResponse
400{"error": "domain parameter is required"}
404{"error": "Domain not found"}

Admin API

All admin endpoints require authentication via the x-api-key header.

Stores

GET /shop/admin/stores
List all stores you have access to.

Response

JSON
{
  "stores": [
    {
      "storeId": "my-store",
      "name": "My Store",
      "tier": "standard",
      "status": "active",
      "domain": "https://mystore.com",
      "contactEmail": "me@mystore.com",
      "currency": "usd",
      "brandColor": "#418a9e",
      "apiKey": "sc_live_...",
      "createdAt": "2026-02-20T10:00:00Z"
    }
  ]
}
POST /shop/admin/stores
Create a new store. Returns the store details and API key.

Request Body

JSON
{
  "storeId": "my-store",
  "name": "My Store",
  "domain": "https://mystore.com",
  "contactEmail": "me@mystore.com",
  "stripePublishableKey": "pk_live_...",
  "stripeSecretKey": "sk_live_...",
  "stripeWebhookSecret": "whsec_...",
  "brandColor": "#418a9e"
}
FieldTypeRequiredDescription
storeIdstringrequired3-32 chars, lowercase alphanumeric + hyphens
namestringoptionalDisplay name
domainstringoptionalYour storefront URL
contactEmailstringoptionalStore contact email
stripePublishableKeystringoptionalStripe publishable key (pk_live_ or pk_test_)
stripeSecretKeystringoptionalStripe secret key (sk_live_ or sk_test_)
stripeWebhookSecretstringoptionalStripe webhook secret (whsec_)
brandColorstringoptionalHex color for widget branding

Response (201)

JSON
{
  "store": { ... },
  "apiKey": "sc_live_abc123...",
  "message": "Store created successfully. Save your API key."
}
PUT /shop/admin/stores/{storeId}
Update store settings. Only include fields you want to change.

Request Body

JSON
{
  "name": "New Store Name",
  "domain": "https://newdomain.com",
  "brandColor": "#4292e7",
  "allowedDomains": ["mystore.com", "www.mystore.com"]
}
Allowed Domains: If set, only these domains can call your public API. Max 10 domains. Leave empty to allow all origins.
POST /shop/admin/stores/{storeId}/regenerate-key
Regenerate your API key. The old key becomes invalid immediately.

Response

JSON
{
  "apiKey": "sc_live_new_key_here...",
  "message": "API key regenerated. Old key is now invalid."
}

Products

POST /shop/{storeId}/admin/products
Create a new product.

Request Body

JSON
{
  "name": "Trucker Surf Hat",
  "price": 2800,
  "description": "Classic trucker hat with embroidered surf logo.",
  "categoryId": "abc-123",
  "compareAtPrice": 3500,
  "inventory": 50,
  "images": ["https://..."],
  "variants": [
    {
      "variantId": "d90da05a",
      "name": "Adjustable / Navy",
      "price": 2800,
      "sku": "TSH-ADJ-NVY",
      "inventory": 25,
      "optionValues": { "Size": "Adjustable", "Color": "Navy" }
    }
  ],
  "options": [
    { "name": "Size", "values": ["Adjustable"] },
    { "name": "Color", "values": ["Navy", "Black"] }
  ],
  "productType": "physical",
  "taxable": true,
  "tags": ["hats"],
  "status": "active",
  "featured": false
}
FieldTypeRequiredDescription
namestringrequiredProduct name
priceintegerrequiredPrice in cents
descriptionstringoptionalHTML description
categoryIdstringoptionalCategory UUID
compareAtPriceintegeroptionalOriginal price for sale display (must be > price)
inventoryintegeroptionalStock quantity. Null = unlimited.
imagesarrayoptionalArray of image URLs
variantsarrayoptionalProduct variants (variantId, name, price, sku, inventory, optionValues). optionValues is a key/value map of option axis name → selected value (e.g. {"Color":"Navy","Size":"Small"}). Used by the widget and web component for multi-axis variant resolution. Legacy flat-name format still supported.
optionsarrayoptionalOption definitions for variant generation
productTypestringoptionalphysical (default) or digital
statusstringoptionaldraft (default), active
featuredbooleanoptionalFeatured product flag
slugstringoptionalURL slug. Auto-generated from name if omitted.
taxablebooleanoptionalSubject to tax (default true)
tagsarrayoptionalString tags for organization
shippingCostintegeroptionalPer-item shipping cost in cents (for per_item method)

Response (201)

JSON
{
  "product": {
    "productId": "dd9392ed-...",
    "slug": "trucker-surf-hat",
    "name": "Trucker Surf Hat",
    ...
  }
}
PUT /shop/{storeId}/admin/products
Update an existing product. Only include fields you want to change.

Request Body

JSON
{
  "productId": "dd9392ed-ea5a-494c-adbc-a8ce5288c2e8",
  "price": 3200,
  "inventory": 40,
  "status": "active"
}
FieldTypeRequiredDescription
productIdstringrequiredProduct UUID to update

All other fields from Create are accepted. Only provided fields are updated.

DELETE /shop/{storeId}/admin/products
Archive a product. It remains in the database but is hidden from public listings.

Request Body

JSON
{
  "productId": "dd9392ed-ea5a-494c-adbc-a8ce5288c2e8"
}

Categories

POST /shop/{storeId}/admin/categories
Create a new category. Set parentId to create a subcategory under an existing parent category.

Request Body

FieldTypeRequiredDescription
namestringrequiredCategory name
descriptionstringoptionalCategory description
imagestringoptionalImage URL (use the upload endpoint to get a URL first)
parentIdstringoptionalSet to a parent categoryId to make this a subcategory. Omit or send empty string for top-level.
sortOrdernumberoptionalDisplay order (default: 0)
statusstringoptionalactive (default) or hidden

Example: Top-level category

JSON
{
  "name": "Men's",
  "description": "Men's clothing and accessories",
  "image": "https://...",
  "sortOrder": 0
}

Example: Subcategory

JSON
{
  "name": "Shirts",
  "description": "Men's shirts",
  "parentId": "cat-001",
  "image": "https://...",
  "sortOrder": 0
}
PUT /shop/{storeId}/admin/categories
Update a category. Only include the fields you want to change (plus categoryId which is always required).

Updatable Fields

name, slug, description, image, parentId, sortOrder, status

Example: Move to subcategory and update sort order

JSON
{
  "categoryId": "cat-002",
  "parentId": "cat-001",
  "sortOrder": 2
}

Example: Promote to top-level

JSON
{
  "categoryId": "cat-002",
  "parentId": ""
}
DELETE /shop/{storeId}/admin/categories
Hide a category. Sets status to "hidden". Note: hiding a parent category does not automatically hide its subcategories.
JSON
{
  "categoryId": "abc-123"
}

Coupons

GET /shop/{storeId}/admin/coupons
List all coupons for this store.
JSON
{
  "coupons": [
    {
      "code": "SAVE20",
      "type": "percent_off",
      "value": 20,
      "minOrderAmount": 5000,
      "maxUses": 100,
      "currentUses": 42,
      "expiresAt": "2026-12-31T23:59:59Z",
      "status": "active"
    }
  ]
}
POST /shop/{storeId}/admin/coupons
Create a new coupon.
JSON
{
  "code": "SAVE20",
  "type": "percent_off",
  "value": 20,
  "minOrderAmount": 5000,
  "maxUses": 100,
  "expiresAt": "2026-12-31T23:59:59Z",
  "status": "active"
}
FieldTypeRequiredDescription
codestringrequiredCoupon code (auto-uppercased)
typestringrequiredpercent_off, fixed_amount, or free_shipping
valueintegerrequiredPercentage (1-100) or cents amount
minOrderAmountintegeroptionalMinimum subtotal in cents
maxUsesintegeroptionalMax number of times coupon can be used
expiresAtstringoptionalISO datetime when coupon expires
PUT /shop/{storeId}/admin/coupons
Update a coupon. Code is required to identify which coupon to update.
JSON
{
  "code": "SAVE20",
  "value": 25,
  "maxUses": 200
}
DELETE /shop/{storeId}/admin/coupons
Deactivate a coupon.
JSON
{
  "code": "SAVE20"
}

Orders

GET /shop/{storeId}/admin/orders
List orders with optional filtering.

Query Parameters

ParameterTypeDescription
statusstringFilter by status (e.g. paid, shipped)
fromstringISO datetime to filter from
tostringISO datetime to filter to
limitintegerResults per page (default 50)
lastKeystringPagination token

Response

JSON
{
  "orders": [
    {
      "orderId": "a1b2c3d4-...",
      "orderNumber": "MYS-1001",
      "email": "customer@example.com",
      "customerName": "John Doe",
      "items": [...],
      "subtotal": 5600,
      "shippingCost": 499,
      "taxAmount": 336,
      "discountAmount": 0,
      "total": 6435,
      "status": "paid",
      "trackingNumber": null,
      "createdAt": "2026-02-24T12:00:00Z"
    }
  ],
  "lastKey": null
}
PUT /shop/{storeId}/admin/orders/{orderId}
Update order status, tracking number, or notes.
JSON
{
  "status": "shipped",
  "trackingNumber": "1Z999AA10123456784",
  "notes": "Shipped via UPS"
}

Settings

GET /shop/{storeId}/admin/settings
Get all store settings (tax, shipping, email, order config).
JSON
{
  "settings": {
    "tax_config": {
      "enabled": true,
      "useStripeTax": false,
      "defaultRate": 6
    },
    "shipping_config": {
      "method": "flat_rate",
      "flatRate": 499,
      "freeShippingEnabled": false,
      "freeShippingThreshold": 5000,
      "defaultItemShippingCost": 499,
      "tiers": [],
      "localPickupEnabled": false,
      "localPickupInstructions": ""
    },
    "email_config": {
      "fromEmail": "noreply@mystore.com",
      "fromName": "My Store"
    },
    "order_config": {
      "orderPrefix": "MYS",
      "nextOrderNumber": 1001
    }
  }
}
PUT /shop/{storeId}/admin/settings
Update a specific setting.
JSON
{
  "settingKey": "shipping_config",
  "value": {
    "method": "flat_rate",
    "flatRate": 599,
    "freeShippingEnabled": true,
    "freeShippingThreshold": 7500,
    "localPickupEnabled": true,
    "localPickupInstructions": "Pick up at 123 Main St, 9AM-5PM"
  }
}
Setting KeyDescription
tax_configSales tax: enabled (master toggle), useStripeTax (automatic Stripe Tax for Stripe checkouts), defaultRate (flat percent applied to PayPal orders, and to Stripe orders when Stripe Tax is off)
shipping_configShipping method, rates, free shipping, local pickup
email_configFrom email address and name for order emails
order_configOrder number prefix and next number
Shipping Methods: flat_rate (single rate), per_item (per product), tiered (by subtotal), free. Free tier stores are limited to flat_rate and free.

Reports

GET /shop/{storeId}/admin/reports
Get sales reports with revenue, order counts, top products, and tax breakdown.

Query Parameters

ParameterTypeDefaultDescription
fromstring2020-01-01Start date (ISO)
tostringnowEnd date (ISO)
statusstringpaid,shipped,deliveredComma-separated order statuses to include

Response

JSON
{
  "period": { "from": "2026-02-01", "to": "2026-02-28" },
  "totalRevenue": 523400,
  "totalOrders": 156,
  "averageOrderValue": 3355,
  "totalTaxCollected": 28450,
  "taxByState": { "SC": 18900, "NC": 6200 },
  "totalShipping": 45200,
  "totalDiscounts": 12300,
  "topProducts": [
    { "productId": "...", "name": "Trucker Hat", "unitsSold": 156, "revenue": 155844 }
  ]
}

Uploads

POST /shop/{storeId}/admin/upload-url
Get a presigned S3 URL for uploading images or digital files. Use the returned fileUrl when creating/updating products or categories.

Request Body

FieldTypeRequiredDescription
fileNamestringrequiredOriginal file name
contentTypestringrequiredMIME type (e.g. image/jpeg, image/png)
uploadTypestringrequiredproduct_image, digital_file, category_image, or storefront_image
fileSizeMBnumberrequiredFile size in megabytes (validated against tier limits)

Upload Types & S3 Paths

uploadTypeS3 PathUse For
product_image{storeId}/products/{file}Product images array
digital_file{storeId}/digital/{file}Downloadable digital products
category_image{storeId}/categories/{file}Category/subcategory card images
storefront_image{storeId}/storefront/{file}Hero images, about page images, logo

Example

JSON
{
  "fileName": "product-photo.jpg",
  "contentType": "image/jpeg",
  "uploadType": "product_image",
  "fileSizeMB": 2.5
}

Response

JSON
{
  "uploadUrl": "https://shoppingcart-assets.s3.amazonaws.com/...?X-Amz-Signature=...",
  "fileUrl": "https://shoppingcart-assets.s3.amazonaws.com/my-store/products/abc.jpg",
  "s3Key": "my-store/products/abc.jpg"
}

Upload your file with a PUT request to uploadUrl with the Content-Type header matching what you specified. Then use fileUrl in your product images array, category image field, etc.

Storefront

GET /shop/{storeId}/admin/storefront
Get the current storefront settings for your store.

Response

JSON
{
  "storefront": {
    "enabled": true,
    "template": "classic",
    "logo": "https://...",
    "showStoreName": true,
    "accentColor": "#418a9e",
    "backgroundColor": "#FFFFFF",
    "textColor": "#333333",
    "heroTitle": "Welcome to My Store",
    "heroLayout": "center",
    "heroImage": "",
    "storeDescription": "Your store description",
    "aboutTitle": "About My Store",
    "aboutContent": "About page content...",
    "aboutImage": "",
    "announcementBar": {
      "enabled": false,
      "text": "",
      "backgroundColor": "#418a9e",
      "textColor": "#FFFFFF"
    },
    "socialLinks": {
      "instagram": "",
      "twitter": "",
      "facebook": "",
      "tiktok": "",
      "youtube": ""
    },
    "showPoweredBy": true
  }
}
PUT /shop/{storeId}/admin/storefront
Update storefront settings. Only include the fields you want to change. Requires an active hosted plan.

Updatable Fields

FieldTypeDescription
templatestringTemplate name: classic, bold, compact
logostringStore logo URL (recommended 240×72px). Use upload endpoint with storefront_image type.
showStoreNamebooleanShow store name text next to logo in nav (default: true)
accentColorstringPrimary accent color hex (e.g. #418a9e)
backgroundColorstringPage background color hex
textColorstringBody text color hex
heroTitlestringHero section heading (defaults to store name)
heroLayoutstringcenter (default), image-left, or image-right
heroImagestringHero image URL (used with image-left/image-right layouts)
storeDescriptionstringHero description text (displayed below title)
aboutTitlestringAbout page heading (defaults to "About {storeName}")
aboutContentstringAbout page body content (line breaks preserved)
aboutImagestringAbout page image URL
announcementBarobject{ enabled, text, backgroundColor, textColor }
socialLinksobject{ instagram, twitter, facebook, tiktok, youtube }
showPoweredBybooleanShow "Powered by Nanocart" in footer
faviconstringFavicon URL

Example

JSON
{
  "heroTitle": "Welcome to My Shop",
  "heroLayout": "image-right",
  "heroImage": "https://...",
  "accentColor": "#e74c3c",
  "aboutTitle": "Our Story"
}

Tier & Usage

GET /shop/{storeId}/admin/tier
Get current tier info, limits, and usage.
JSON
{
  "tier": "standard",
  "limits": {
    "maxProducts": 25,
    "maxMonthlyOrders": 500,
    "maxCoupons": 5,
    "maxUploadMB": 25,
    "allowedShippingMethods": ["flat_rate", "per_item", "tiered", "free"],
    "localPickup": true
  },
  "usage": {
    "activeProducts": 12,
    "activeCoupons": 2,
    "monthlyOrders": 47
  }
}

Subscriptions

POST /shop/admin/stores/{storeId}/subscribe
Create a Stripe subscription checkout session to upgrade your plan.
JSON
{
  "tier": "pro",
  "billingPeriod": "monthly"
}
FieldTypeOptions
tierstringstandard, pro, expert
billingPeriodstringmonthly, annual

Response

JSON
{
  "sessionUrl": "https://checkout.stripe.com/c/pay/..."
}
GET /shop/admin/stores/{storeId}/subscription
Get current subscription details.
JSON
{
  "tier": "pro",
  "subscriptionId": "sub_...",
  "subscriptionStatus": "active",
  "billingPeriod": "monthly",
  "cancelAtPeriodEnd": false
}
POST /shop/admin/stores/{storeId}/cancel-subscription
Cancel subscription at end of billing period. Store keeps current tier until then.
JSON
{
  "message": "Subscription will cancel at end of billing period."
}
POST /shop/admin/stores/{storeId}/reactivate-subscription
Reactivate a subscription that was scheduled for cancellation. Undoes a cancel without charging anything — the next renewal date is unchanged.

Response

JSON
{
  "message": "Your Standard Widget plan has been reactivated. Your next renewal is unchanged."
}

Errors

StatusResponse
400{"error": "No subscription found."}
400{"error": "Your subscription is not scheduled for cancellation."}

Webhooks

Webhooks let nanocart push real-time events to your server when things happen in your store. Configure a webhook URL in the admin panel under Settings → Webhooks.

Verification

Every request includes three headers you should verify before processing:

HeaderDescription
x-nanocart-signaturesha256=<HMAC-SHA256 hex digest> — computed over the raw request body using your webhook secret
x-nanocart-timestampUnix timestamp (seconds) of delivery. Reject if >300s old to prevent replay attacks.
x-nanocart-deliveryUnique delivery ID (UUID) for idempotency
Node.js
const crypto = require('crypto');

function verifyWebhook(req, secret) {
  const sig = req.headers['x-nanocart-signature'] || '';
  const ts  = req.headers['x-nanocart-timestamp'] || '';

  // Reject stale deliveries
  if (Math.abs(Date.now() / 1000 - parseInt(ts, 10)) > 300) return false;

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)        // raw Buffer/string, before JSON.parse
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

app.post('/webhooks/nanocart', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyWebhook(req, process.env.NANOCART_WEBHOOK_SECRET)) {
    return res.status(401).send('Unauthorized');
  }
  const event = JSON.parse(req.body);
  // handle event.type ...
  res.sendStatus(200);
});

Event Types

All events share a common envelope:

JSON
{
  "eventType": "order.created",
  "storeId": "my-store",
  "timestamp": "2026-06-17T14:00:00Z",
  "data": { ... }
}
EVENT order.created
Fired when a Stripe checkout session completes and an order is recorded.
JSON
{
  "eventType": "order.created",
  "storeId": "my-store",
  "timestamp": "2026-06-17T14:00:00Z",
  "data": {
    "orderId": "a1b2c3d4-...",
    "orderNumber": "MYS-1001",
    "email": "customer@example.com",
    "customerName": "John Doe",
    "items": [
      { "productId": "...", "name": "Trucker Hat", "variantName": "Navy", "price": 2800, "quantity": 2 }
    ],
    "subtotal": 5600,
    "shippingCost": 499,
    "taxAmount": 336,
    "discountAmount": 0,
    "total": 6435,
    "status": "paid",
    "createdAt": "2026-06-17T14:00:00Z"
  }
}
EVENT order.shipped
Fired when an order status is updated to shipped (via PUT /admin/orders/{orderId}).
JSON
{
  "eventType": "order.shipped",
  "storeId": "my-store",
  "timestamp": "2026-06-17T16:00:00Z",
  "data": {
    "orderId": "a1b2c3d4-...",
    "orderNumber": "MYS-1001",
    "email": "customer@example.com",
    "trackingNumber": "1Z999AA10123456784",
    "status": "shipped"
  }
}
EVENT order.cancelled
Fired when an order status is updated to cancelled.
JSON
{
  "eventType": "order.cancelled",
  "storeId": "my-store",
  "timestamp": "2026-06-17T17:00:00Z",
  "data": {
    "orderId": "a1b2c3d4-...",
    "orderNumber": "MYS-1001",
    "email": "customer@example.com",
    "status": "cancelled"
  }
}
EVENT product.low_stock
Fired when a product variant's inventory drops to 5 or below after a completed order.
JSON
{
  "eventType": "product.low_stock",
  "storeId": "my-store",
  "timestamp": "2026-06-17T14:01:00Z",
  "data": {
    "productId": "dd9392ed-...",
    "productName": "Trucker Surf Hat",
    "slug": "trucker-surf-hat",
    "variantId": "d90da05a",
    "variantName": "Adjustable / Navy",
    "inventory": 3
  }
}
EVENT checkout.abandoned
Fired when a Stripe checkout session expires without payment (after ~30 min).
JSON
{
  "eventType": "checkout.abandoned",
  "storeId": "my-store",
  "timestamp": "2026-06-17T14:35:00Z",
  "data": {
    "orderId": "a1b2c3d4-...",
    "email": "customer@example.com",
    "items": [
      { "productId": "...", "name": "Trucker Hat", "quantity": 1, "price": 2800 }
    ],
    "subtotal": 2800
  }
}
EVENT webhook.test
Sent when you click "Send test event" in the admin panel. Use it to confirm your endpoint is reachable and your signature verification is correct.
JSON
{
  "eventType": "webhook.test",
  "storeId": "my-store",
  "timestamp": "2026-06-17T14:00:00Z",
  "data": {
    "message": "This is a test event from nanocart."
  }
}

Error Codes

Error responses include an error message and an optional code for programmatic handling:

JSON
{
  "error": "Free plan allows 2 active products. Upgrade to Standard for 25.",
  "code": "TIER_PRODUCT_LIMIT",
  "tierLimit": true
}
CodeHTTPDescription
INVALID_API_KEY401API key doesn't exist or is invalid
STORE_SUSPENDED403Store has been suspended
INVALID_INPUT400Missing or malformed request data
STRIPE_NOT_CONFIGURED400Store has no Stripe keys set up
STRIPE_INVALID_KEYS400Stripe key format is wrong
STRIPE_AUTH_ERROR400Stripe rejected the keys
DOMAIN_NOT_ALLOWED403Origin domain not in allowedDomains
TIER_PRODUCT_LIMIT403Exceeded active product limit for tier
TIER_ORDER_LIMIT403Exceeded monthly order limit for tier
TIER_COUPON_LIMIT403Exceeded coupon limit for tier
TIER_UPLOAD_LIMIT403File size exceeds tier upload limit
TIER_SHIPPING_RESTRICTED403Shipping method not available on tier
DUPLICATE_COUPON409Coupon code already exists
Tier limit errors include "tierLimit": true so your app can detect upgrade prompts. The error message includes the current limit and the next tier's limit.

© 2026 NanoCart · a ByteBunny, LLC company  |  Dashboard  |  hello@nanocart.io

Last updated: July 2026