All API routes are located in app/api/ and follow Next.js 16 App Router conventions.
Description: Add an email address to the deals newsletter. Addresses are normalised, deduplicated and persisted to subscribers.json (see DEALSHUB_DATA_DIR). When WEB3FORMS_ACCESS_KEY is set the address is also forwarded to Web3Forms.
POST /api/subscribe
Content-Type: application/json
{
"email": "user@example.com",
"source": "footer"
}Success (200):
{ "ok": true }Re-submitting a known address returns the same shape with "alreadySubscribed": true.
Error (400): Missing or invalid email
{ "ok": false, "error": "Please enter a valid email address" }Error (429): Rate limit exceeded (5 requests per minute per IP)
const subscribe = async (email: string) => {
const response = await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, source: 'footer' }),
})
const data = await response.json()
if (!response.ok || !data.ok) {
throw new Error(data.error ?? 'Subscription failed')
}
return data
}Note: the legacy
POST /api/newsletterpath never existed in this repository β use/api/subscribe.
Records a first-party analytics event (product views, affiliate clicks, search terms). Persistence is best-effort so tracking can never break a user request.
POST /api/track
Content-Type: application/json
{
"event": "product_view",
"productId": 1,
"source": "home",
"category": "electronics",
"pathname": "/product/1"
}{ "ok": true, "persisted": true }Returns aggregated analytics: { totalEvents, totals, latest }.
Access rules:
| Environment | ANALYTICS_READ_TOKEN |
Result |
|---|---|---|
| production | set | requires Authorization: Bearer <token> or ?token=<token> |
| production | unset | 403 β read access is disabled |
| development | either | open, for local tooling |
These routes were consolidated and now return permanent redirects instead of leaking credential details:
| Path | Redirects to |
|---|---|
GET /api/ebay-status |
/api/ebay/status (308) |
GET /api/ebay-test |
/api/health (308) |
GET /api/debug/ebay-status |
/api/health (308) |
GET /api/test/ebay-finding |
/api/health (308) |
Description: Search eBay products using the consolidated eBay API.
GET /api/ebay/search?q=iphone&category=electronics&limit=20| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
q |
string | Yes | - | Search query |
category |
string | No | - | Product category filter |
limit |
number | No | 20 | Results per page (max 100) |
offset |
number | No | 0 | Pagination offset |
sort |
string | No | BestMatch |
Sort order |
condition |
string | No | - | New, Used, or Refurbished |
Success (200):
{
"items": [
{
"id": "123456789",
"title": "iPhone 15 Pro Max",
"price": 1199.99,
"currency": "USD",
"image": "https://i.ebayimg.com/...",
"url": "https://ebay.com/itm/...",
"condition": "New",
"shipping": "Free",
"location": "United States"
}
],
"total": 1250,
"hasMore": true
}Error (400): Missing or invalid parameters
{
"error": "Search query is required"
}Error (429): Rate limit exceeded
{
"error": "eBay API rate limit exceeded"
}Error (500): Server error
{
"error": "Failed to search products"
}Currently, the API does not require authentication for public endpoints.
Future: When user accounts are added, we'll use:
- JWT tokens for session management
- OAuth 2.0 for third-party auth
- API keys for programmatic access
| Endpoint | Limit | Window | Status |
|---|---|---|---|
/api/newsletter |
5 requests | 15 minutes | β Active |
/api/ebay/* |
100 requests | 1 hour | β Active |
/api/* (default) |
1000 requests | 1 hour | β Active |
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 1640995200
All errors follow this structure:
interface ErrorResponse {
error: string // Human-readable error message
code?: string // Machine-readable error code
details?: any // Additional error context
timestamp?: string // ISO 8601 timestamp
}| Code | Meaning | Usage |
|---|---|---|
200 |
OK | Successful request |
201 |
Created | Resource created |
400 |
Bad Request | Invalid input |
401 |
Unauthorized | Authentication required |
403 |
Forbidden | No permission |
404 |
Not Found | Resource not found |
409 |
Conflict | Resource already exists |
429 |
Too Many Requests | Rate limit exceeded |
500 |
Internal Server Error | Server error |
503 |
Service Unavailable | Temporary outage |
- All inputs are validated before processing
- Email addresses checked with regex
- SQL injection prevention (parameterized queries)
- XSS prevention (sanitized outputs)
// Allowed origins
const allowedOrigins = [
'https://www.saleh-store.com',
'http://localhost:3000' // Development only
]X-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGINX-XSS-Protection: 1; mode=block
Currently: v1 (implicit)
Future: API versioning will be added:
/api/v1/newsletter/api/v2/newsletter
All API requests are logged with:
- Timestamp
- Method and path
- IP address
- User agent
- Response status
- Response time
Tracked via Vercel Analytics:
- Request count
- Error rate
- Response time (p50, p95, p99)
- Rate limit hits
Last Updated: February 16, 2026
API Version: 1.0
Status: Production Ready β