API Documentation

The Storefront API is a read-only service that powers product search, filtering, and category browsing for the storefront. It reads from a shared Neon PostgreSQL database and caches frequently accessed data in Upstash Redis.

Base URL: https://storefront-api-eight.vercel.app
Local: http://localhost:3000

Architecture

Browser / Storefront
        │
        ▼
  Vercel CDN (Edge)     ← HTTP Cache-Control headers
        │
        ▼
  Next.js Serverless    ← API routes (us-east-1)
        │
   ┌────┴────┐
   ▼         ▼
 Neon DB   Upstash Redis
(shared)    (shared)
   │         │
   └────┬────┘
        │
  shift4shop-sync       ← Writes to DB, invalidates cache
  • Next.js 14 — API routes deployed as Vercel serverless functions
  • Neon PostgreSQL — ~50k products, connection pool (max 10), auto-suspend enabled
  • Upstash Redis — caches expensive queries (filters, products, settings)
  • Vercel CDN — edge caching via Cache-Control headers on API responses

DB Fallback

When Neon PostgreSQL is unavailable, the API automatically falls back to the Shift4Shop REST API so the live site continues serving product data.

Normal:    Request → Redis → Neon DB ✓
Redis down: Request → Neon DB ✓ (cache miss)
Neon down:  Request → Redis miss → Shift4Shop API ✓

Routes with fallback

  • /api/products and /api/filters — full fidelity (in-memory filtering)
  • /api/categories/[id]/settings — full fidelity

How to detect fallback

  • _source field in response body — "db" or "api"
  • X-Data-Source response header
  • Vercel Runtime Logs: [fallback] Serving /api/... from Shift4Shop API

Limitations in fallback mode

  • Higher latency (~500ms–1s vs ~50ms from Neon)
  • Rate limited (~60 req/min on Shift4Shop API)

Security

Authentication

Public catalog routes require no authentication. Dashboard and admin routes require a session cookie obtained via /api/auth/login.

Protected routes

  • POST /api/cache — cache invalidation
  • POST /api/fallback/override — fallback mode control
  • /api/auth/users — user management
  • /dashboard/* — all dashboard pages (redirects to /login)

CORS

All /api/* routes are protected by CORS origin allowlisting, enforced in middleware.

Allowed origins

  • Origins listed in ALLOWED_ORIGINS env var
  • Vercel deployment URLs (auto-detected)
  • localhost:3000 and localhost:3001 in development

Same-origin requests

Requests without an Origin header are always allowed (server-to-server, curl, etc.)

Rate Limiting

Public API routes are rate limited at 60 requests per minute per IP address using a Redis fixed-window counter.

  • Returns 429 Too Many Requests with Retry-After header when exceeded
  • Fail-open design — if Redis is unavailable, requests pass through
  • Auth routes (/api/auth/*) are excluded from rate limiting

Error Responses

All error responses return generic messages — internal error details are never exposed to clients.

Caching Strategy

Three-tier caching reduces database load and improves response times:

LayerWhereDetails
Vercel CDNEdge (global)Honors Cache-Control headers (30-60s on search routes)
Upstash RedisApplication5-10min TTL on filters, products, category settings
Neon PostgreSQLOriginConnection pool (max 10), query-level caching by Neon
Cache invalidation: The shift4shop-sync project automatically calls POST /api/cache after product syncs. Manual invalidation is available from the dashboard.

Data Model

Key field mappings from the Shift4Shop data model:

DB FieldDisplay LabelNotes
extra_field1(Sort Order)Numeric sort weight — used for default product ordering
extra_field2Lead Timee.g., “Ships in 3-5 days”
extra_field3Assembly Typee.g., “Ready To Assemble”, “Pre-Assembled”
extra_field4Made in the USA“Yes” / “No”
extra_field5Door Stylee.g., “Shaker”, “Raised Panel”
manufacturer_nameSeries Namee.g., “Elegance Series”
distributor_listColorJSONB array — extracted via DistributorName field
category_specialBest SellerBoolean — enables best_seller sort option
has_related(computed)Boolean — true if product has related products in product_related table
related_catalogid(computed)ID of first related product — used for preview prefetch on hover
non_searchable(visibility)Visible in category browse, hidden from full-site search

API Reference

Detailed documentation for each endpoint.

GET/api/search/filtersRedis — 5 minute TTL (key built from sorted params)

Filter Options

Returns available filter values with product counts, contextual to currently active filters. Each filter group's counts reflect all other active filters (cross-filter counting).

Parameters

ParameterTypeDescription
qstringSearch keyword (same as /api/search)
category_idintegerScope to a category
manufacturer_namestringActive Series Name filter(s)
colorstringActive Color filter(s)
extra_field2stringActive Lead Time filter(s)
extra_field3stringActive Assembly Type filter(s)
extra_field4stringActive Made in USA filter(s)
extra_field5stringActive Door Style filter(s)
min_pricenumberMinimum price filter
max_pricenumberMaximum price filter

Example

/api/search/filters?category_id=1796&extra_field3=Ready+To+Assemble

Response

{
  "success": true,
  "price_range": { "min": 49.99, "max": 3299.99 },
  "categories": [
    { "id": 1796, "name": "RTA Kitchen Cabinets", "count": 70 }
  ],
  "filter_groups": [
    {
      "key": "manufacturer_name",
      "label": "Series Name",
      "type": "checkbox",
      "options": [
        { "value": "Elegance Series", "label": "Elegance Series", "count": 12 },
        { "value": "Heritage Series", "label": "Heritage Series", "count": 8 }
      ]
    },
    {
      "key": "extra_field5",
      "label": "Door Style",
      "type": "checkbox",
      "options": [
        { "value": "Shaker", "label": "Shaker", "count": 35 },
        { "value": "Raised Panel", "label": "Raised Panel", "count": 15 }
      ]
    }
  ]
}

Notes

  • Runs 6+ parallel queries (price range, each filter field, categories, features)
  • Zero-count options are excluded from results
  • Cross-filter counting: selecting "Shaker" updates Color counts but Door Style still shows all options
GET/api/search/suggest30s HTTP cache

Autocomplete Suggestions

Returns categorized autocomplete suggestions for products, categories, and SKUs. Designed for search-as-you-type UI.

Parameters

ParameterTypeDescription
q*stringSearch term (minimum 2 characters)
category_idintegerOptional scope to a category

Example

/api/search/suggest?q=modern

Response

{
  "success": true,
  "suggestions": [
    { "text": "Modern White Base 24\"", "type": "product", "id": 62143, "meta": "$189.99" },
    { "text": "Kitchen Products", "type": "category", "id": 1796, "meta": "70 products" },
    { "text": "MW-B24", "type": "sku", "id": 62143, "meta": "Modern White Base" }
  ]
}

Notes

  • Returns up to 3-5 results per type (product, category, SKU)
  • Uses both full-text search and ILIKE pattern matching
  • Excludes hidden and non_searchable products
GET/api/preview60s HTTP cache

Product Quick-View

Fetches full product details plus its first related product for modal previews. Includes images and feature attributes. Shared module — used by both search and category browsing.

Parameters

ParameterTypeDescription
id*integerProduct catalog ID

Example

/api/preview?id=62143

Response

{
  "success": true,
  "product": {
    "id": 62143,
    "name": "Modern White 10x10 Set",
    "short_description": "...",
    "description": "...",
    "price": 1899.99,
    "sale_price": 1499.99,
    "on_sale": true,
    "main_image": "/assets/images/product-main.jpg",
    "thumbnail": "/assets/images/product-thumb.jpg",
    "manufacturer_name": "Elegance Series",
    "extra_field1": "10", "extra_field2": "Ships in 3-5 days",
    "extra_field3": "Ready To Assemble", "extra_field4": "Yes",
    "extra_field5": "Shaker",
    "sample_enable": 1, "sample_name": "Sample", "sample_price": 4.99,
    "images": [
      { "image_file": "/assets/images/product-1.jpg", "image_caption": "Front view" }
    ],
    "features": [
      { "feature_name": "Collection", "feature_value": "Elegance" },
      { "feature_name": "Color Family", "feature_value": "White" }
    ]
  },
  "relatedProduct": {
    "id": 62144,
    "name": "Sample Product",
    "...": "same structure as product"
  }
}

Notes

  • Related products linked via product_related table (relationship_type = related)
  • relatedProduct is the first related product found (if any)
  • Fetches product_images and product_features for both the main product and related product
  • Use has_related + related_catalogid from listing APIs to prefetch on hover
GET/api/productsRedis — 5 minute TTL (key: products:category:{id}:{params})

Products (Data Layer)

Raw paginated products for a category. Returns product data only — no filter options. Use /api/filters for products + filter options together. Falls back to Shift4Shop REST API when DB is down.

Parameters

ParameterTypeDescription
category*integerCategory ID
pageintegerPage number (default: 1)
limitintegerResults per page, max 100 (default: 6)
sortstringSort: featured (default), price_asc, price_desc, name_asc, name_desc, newest, best_seller
idsstringComma-separated product IDs for native Shift4Shop sort order
[filter_name]stringAny other param is treated as a filter. Multi-value supported: repeat param for OR logic (e.g., Color=White&Color=Gray).

Example

/api/products?category=1796&limit=12&sort=featured&Color=White

Response

{
  "products": [
    {
      "catalogid": 62143,
      "sku": "MW-10X10",
      "name": "Modern White 10x10 Set",
      "price": 1899.99,
      "sale_price": 1499.99,
      "on_sale": true,
      "main_image": "/assets/images/product-main.jpg",
      "thumbnail": "/assets/images/product-thumb.jpg",
      "manufacturer_name": "Elegance Series",
      "color": "White",
      "category_special": false,
      "stock": 25,
      "extra_field1": "10", "extra_field2": "Ships in 3-5 days",
      "has_related": true,
      "related_catalogid": 62144,
      "features": {}
    }
  ],
  "total": 33,
  "page": 1,
  "totalPages": 3,
  "_source": "db"
}

Notes

  • Data layer endpoint — returns products only, no filter options
  • Falls back to Shift4Shop REST API when Neon DB is down — check _source field
  • The ids param supports Shift4Shop's native sort order via array_position()
  • best_seller sort uses category_special boolean column
GET/api/filtersRedis — 5 minute TTL (key built from sorted params, excludes ids)

Category Filters (Composite)

Composite endpoint: returns paginated products AND dynamic filter options for a category in a single request. Auto-detects filter mode: uses product_features if available, otherwise filters on extra_field columns and distributor_list. Ensures data consistency between products and filter counts.

Parameters

ParameterTypeDescription
category*integerCategory ID
pageintegerPage number (default: 1)
limitintegerResults per page, max 100 (default: 6)
sortstringSort: featured (default), price_asc, price_desc, name_asc, name_desc, newest, best_seller
idsstringComma-separated product IDs for native Shift4Shop sort order
[filter_name]stringAny other param is treated as a filter. Multi-value supported: repeat param for OR logic (e.g., Color=White&Color=Gray). Filter names match labels: Lead Time, Assembly Type, Series, Color, etc.

Example

/api/filters?category=1796&limit=12&sort=featured&Color=White&Color=Gray

Response

{
  "products": [
    {
      "catalogid": 62143,
      "sku": "MW-10X10",
      "name": "Modern White 10x10 Set",
      "price": 1899.99,
      "sale_price": 1499.99,
      "on_sale": true,
      "main_image": "/assets/images/product-main.jpg",
      "thumbnail": "/assets/images/product-thumb.jpg",
      "manufacturer_name": "Elegance Series",
      "color": "White",
      "category_special": false,
      "stock": 25,
      "extra_field1": "10", "extra_field2": "Ships in 3-5 days",
      "has_related": true,
      "related_catalogid": 62144,
      "features": {}
    }
  ],
  "filters": [
    {
      "name": "Color",
      "options": [
        { "value": "White", "count": 13 },
        { "value": "Gray", "count": 20 }
      ]
    }
  ],
  "total": 33,
  "page": 1,
  "totalPages": 3,
  "_source": "db"
}

Notes

  • Composite endpoint — products + filter options in one call for data consistency
  • Auto-detects filter mode: product_features (e.g., flooring) or extra_field columns (e.g., RTA cabinets)
  • Multi-value filters: repeat the param for OR logic (e.g., Color=White&Color=Gray)
  • The ids param supports Shift4Shop's native sort order via array_position()
  • Falls back to Shift4Shop REST API when Neon DB is down — check _source field
  • best_seller sort uses category_special boolean column
GET/api/categories/[id]No caching (direct DB query)

Category Products (Legacy)

Legacy API for category product listings with filtering. Still used by the live Shift4Shop storefront.

Parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerResults per page (default: 20)
sortBystringSort field: catalogid, name, price, sku
sortOrderstringSort direction: asc, desc
extra_field1-5stringMulti-select filter arrays
min_pricenumberMinimum price filter
max_pricenumberMaximum price filter
feature_*stringProduct feature filters (e.g., feature_Number_of_Doors=2)

Example

/api/categories/27263?page=1&limit=20&sortBy=price&sortOrder=asc

Response

{
  "success": true,
  "category": {
    "category_id": 27263,
    "name": "Flooring",
    "description": "...",
    "product_count": 12
  },
  "products": [
    {
      "id": 62200, "name": "...", "price": 3.49, "sale_price": 2.99, "on_sale": true,
      "main_image": "...", "thumbnail": "...", "sku": "...", "stock": 100,
      "manufacturer_name": "...", "color": "Nordic Blonde",
      "extra_field1": "28 mil", "has_related": false, "related_catalogid": null
    }
  ],
  "pagination": {
    "page": 1, "limit": 20, "total": 12, "totalPages": 1,
    "hasNextPage": false, "hasPrevPage": false
  },
  "filters": { "sortBy": "price", "sortOrder": "asc" }
}

Notes

  • Legacy endpoint — newer integrations should use /api/search with category_id
  • sortBy values are sanitized to prevent SQL injection
GET/api/categories/[id]/settingsRedis — 10 minute TTL

Category Settings

Returns category-specific display configuration including default sort order, products per page, grid columns, and display title.

Example

/api/categories/27263/settings

Response

{
  "category_id": 27263,
  "category_name": "Flooring",
  "default_products_sorting": 0,
  "items_per_page": 9,
  "product_columns": 3,
  "title": "Luxury Vinyl Plank Flooring"
}
GET/api/categories/tree5 minute HTTP cache (Cache-Control: s-maxage=300, stale-while-revalidate=600)

Category Tree

Returns child categories under a parent with product and subcategory counts. Used by the category explorer in the dashboard.

Parameters

ParameterTypeDescription
parent_idintegerParent category ID (default: 0 for root categories)

Example

/api/categories/tree?parent_id=0

Response

{
  "success": true,
  "parent_id": 0,
  "categories": [
    {
      "category_id": 20475,
      "category_name": "Shop All Products",
      "parent_id": 0,
      "hide": false,
      "filter_cat": false,
      "sorting": 10,
      "is_main": false,
      "product_count": 2,
      "child_count": 0
    }
  ]
}
GET/api/categories5 minute HTTP cache (Cache-Control: s-maxage=300, stale-while-revalidate=600)

Category List

Flat list of all non-hidden categories with product counts.

Example

/api/categories

Response

{
  "success": true,
  "data": [
    { "id": 1796, "name": "Kitchen Cabinets", "parent_id": 327, "hide": false, "product_count": 174 }
  ]
}
POST/api/cacheN/A

Cache Invalidation

Clears all storefront Redis cache keys. Called automatically by the sync project after product writes, or manually from the dashboard.

Example

POST /api/cache

Response

{
  "success": true,
  "deleted": 42
}

Notes

  • Invalidates keys matching prefixes: storefront:*, products:*, filters:*, category:settings:*
  • Requires dashboard session cookie (returns 401 if not authenticated)
GET/api/healthNo caching (real-time probe)

Health Check

Probes database and Redis connectivity with latency measurements and data counts.

Example

/api/health

Response

{
  "status": "healthy",
  "db": { "ok": true, "latencyMs": 12 },
  "redis": { "ok": true, "latencyMs": 8 },
  "counts": { "products": 50000, "categories": 150 },
  "timestamp": "2026-03-22T..."
}

Notes

  • Returns "degraded" if either DB or Redis is unreachable
  • Probes both services independently — one can fail without blocking the other
Storefront API Documentation← Back to home