API Integration
Integrating with the Jose Madrid Salsa REST API
API Integration
This guide covers how to integrate with the Jose Madrid Salsa API, including authentication, available endpoints, rate limiting, and response formats.
API Overview
The API is built with Next.js Route Handlers in the app/api/ directory. All endpoints return JSON responses and use standard HTTP status codes.
Authentication
Session-Based (Web App)
The web app uses NextAuth.js JWT sessions. Session tokens are sent automatically via cookies.
Partner API Keys
For external integrations, use Partner API keys:
# Create a partner API key
npm run api-keys:createPartner API keys are stored in the PartnerApiKey table and can be managed at /admin/settings.
Products API
List Products
GET /api/productsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
heatLevel | string | Filter by heat level: MILD, MEDIUM, HOT, EXTRA_HOT |
search | string | Case-insensitive search in name and description |
featured | true | Only featured products |
categories | string | Comma-separated category slugs |
tags | string | Comma-separated tag slugs |
inStock | true/1 | Only in-stock products |
take | number | Limit results |
skip | number | Offset for pagination |
sortOrder | asc/desc | Sort direction |
Example:
const response = await fetch('/api/products?heatLevel=HOT&featured=true&take=10')
const products = await response.json()Response:
[
{
"id": "clxx...",
"name": "Ghost of Clovis",
"slug": "ghost-of-clovis",
"description": "Smoky ghost peppers...",
"price": 9.49,
"compareAtPrice": 11.49,
"featuredImage": "/images/new-products/ghost-clovis.png",
"images": [...],
"heatLevel": "HOT",
"sku": "JMS-HOT-001",
"inventory": 80,
"isFeatured": true,
"tags": ["spicy", "ghost-pepper"],
"nutritionalInfo": { ... }
}
]Fallback Products
If the database is unavailable, the products API returns a hardcoded set of mock products to keep the storefront functional.
Get Single Product
GET /api/products/[id]Returns a single product with full details including variants, nutritional info, and ingredients.
Orders API
Create Order
POST /api/ordersRequires authentication. Request body:
{
"cartItemIds": ["clxx...", "clxx..."],
"shippingAddress": {
"address1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94111",
"country": "US"
},
"notes": "Leave at front door"
}Response:
{
"clientSecret": "pi_xxx_secret_xxx",
"orderId": "clxx...",
"orderNumber": "JMS-20240115-A1B2C3D4",
"amount": 28.33
}The clientSecret is used to confirm the Stripe PaymentIntent on the client.
List My Orders
GET /api/orders?status=SHIPPED&sortOrder=descReturns orders for the authenticated user.
Rate Limiting
All API endpoints are rate-limited. Exceeding the limit returns a 429 Too Many Requests response.
| Endpoint Type | Limit |
|---|---|
| General API | 100 requests / minute |
| AI Chat | 20 requests / minute (50 for authenticated) |
| Login | 5 attempts / 15 minutes |
| Password Reset | 3 requests / hour |
Rate limit information is included in response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 45Error Handling
All API errors follow a consistent format:
{
"error": "Human-readable error message",
"details": { ... }
}HTTP Status Codes:
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Validation error (check details) |
| 401 | Not authenticated |
| 403 | Insufficient permissions |
| 429 | Rate limit exceeded |
| 500 | Server error |
Input Validation
All POST/PUT endpoints validate input with Zod schemas. Invalid requests return 400 with a flattened error object:
{
"error": "Invalid order payload",
"details": {
"fieldErrors": {
"cartItemIds": ["Required"]
},
"formErrors": []
}
}Webhooks
Stripe Webhooks
POST /api/webhooks/stripeHandles payment events (payment succeeded, failed, refunded). Requires STRIPE_WEBHOOK_SECRET for signature verification.
Shopify Webhooks
POST /api/webhooks/shopifyHandles order sync events from Shopify.
Working with the API in Code
Server-Side (Server Components / Actions)
Use Prisma directly instead of calling the API:
import { prisma } from '@/lib/prisma'
const products = await prisma.product.findMany({
where: { isActive: true },
})Client-Side (React Components)
Use fetch to call API routes:
'use client'
import { useEffect, useState } from 'react'
function ProductList() {
const [products, setProducts] = useState([])
useEffect(() => {
fetch('/api/products?featured=true')
.then(res => res.json())
.then(setProducts)
}, [])
return (/* render products */)
}External Services
For external integrations, include the API key:
const response = await fetch('https://josemadrid.net/api/products', {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
})Admin API
Admin endpoints under /api/admin/ require ADMIN, DEVELOPER, or STAFF roles. These provide CRUD operations for:
- Products (
/api/admin/products) - Orders (
/api/admin/orders) - Users (
/api/admin/users) - Categories (
/api/admin/categories) - Email templates (
/api/admin/email-templates) - Settings (
/api/admin/settings)
How is this guide?
Last updated on