Endpoint
GET /api/hub/catalogs
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | — | Search query (matches name, description, tags) |
category | string | — | Filter by category slug (e.g., apparel, electronics, home-goods) |
sort | string | popular | Sort order: popular, recent, score |
minScore | number | — | Minimum average optimization score (0–100) |
price | string | — | Filter by pricing: free or paid |
limit | number | 20 | Results per page (max: 100) |
offset | number | 0 | Pagination offset |
Response
{
"catalogs": [
{
"id": "hub_cat_9x8k2m",
"name": "Spring 2026 Apparel",
"description": "500+ SKUs of curated spring apparel from top European brands.",
"publisher": {
"workspaceId": "ws_abc",
"displayName": "EuroFashion Wholesale",
"verified": true
},
"productCount": 512,
"price": {
"model": "free"
},
"category": "apparel",
"tags": ["spring", "2026", "european", "fashion"],
"score": 78,
"subscribers": 34,
"publishedAt": "2026-02-01T09:00:00Z",
"lastUpdatedAt": "2026-03-10T14:30:00Z",
"version": 5
}
],
"total": 147,
"hasMore": true
}
HubCatalog fields
| Field | Type | Description |
|---|---|---|
id | string | Hub catalog ID |
name | string | Display name |
description | string | Short description (max 140 chars in card view) |
publisher.workspaceId | string | Publisher’s workspace ID |
publisher.displayName | string | Publisher’s display name |
publisher.verified | boolean | Whether the publisher is verified |
productCount | number | Total products in the catalog |
price.model | string | "free" or "paid" |
price.amount | number | Price in USD (only for "paid" catalogs) |
price.interval | string | "one_time" or "monthly" (only for "paid") |
category | string | Primary category slug |
tags | string[] | Up to 10 tags |
score | number | Average optimization score (0–100) |
subscribers | number | Active subscriber count |
publishedAt | string | ISO 8601 timestamp of first publication |
lastUpdatedAt | string | ISO 8601 timestamp of last version |
version | number | Current version number |
Pagination fields
| Field | Type | Description |
|---|---|---|
total | number | Total matching catalogs (ignoring limit/offset) |
hasMore | boolean | Whether more results are available |
Examples
# Browse all catalogs (default: sorted by popular, 20 per page)
curl "https://app.alana.shopping/api/hub/catalogs"
# Search for apparel catalogs with a high score
curl "https://app.alana.shopping/api/hub/catalogs?q=apparel&category=apparel&sort=score&minScore=70&limit=50"
# Get free catalogs sorted by most recent
curl "https://app.alana.shopping/api/hub/catalogs?price=free&sort=recent&limit=20"
# Paginate: second page
curl "https://app.alana.shopping/api/hub/catalogs?limit=20&offset=20"
// Search for electronics catalogs
const params = new URLSearchParams({
q: 'electronics',
category: 'electronics',
sort: 'score',
minScore: '65',
limit: '20',
offset: '0',
});
const response = await fetch(
`https://app.alana.shopping/api/hub/catalogs?${params}`
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const { catalogs, total, hasMore } = await response.json();
console.log(`Found ${total} catalogs (${catalogs.length} returned)`);
catalogs.forEach(cat => {
console.log(`${cat.name} — ${cat.productCount} products — score: ${cat.score}`);
});
// Load more pages
if (hasMore) {
const nextParams = new URLSearchParams({ ...Object.fromEntries(params), offset: '20' });
const nextResponse = await fetch(
`https://app.alana.shopping/api/hub/catalogs?${nextParams}`
);
const nextPage = await nextResponse.json();
}
import requests
# Search for apparel catalogs sorted by score
response = requests.get(
"https://app.alana.shopping/api/hub/catalogs",
params={
"q": "apparel",
"category": "apparel",
"sort": "score",
"minScore": 70,
"limit": 50,
}
)
response.raise_for_status()
data = response.json()
print(f"Found {data['total']} matching catalogs")
for catalog in data["catalogs"]:
price = catalog["price"]["model"]
print(f"{catalog['name']} — {catalog['productCount']} products — {price} — score: {catalog['score']}")
# Paginate through all results
def get_all_catalogs(**params):
all_catalogs = []
offset = 0
while True:
response = requests.get(
"https://app.alana.shopping/api/hub/catalogs",
params={**params, "limit": 100, "offset": offset}
)
data = response.json()
all_catalogs.extend(data["catalogs"])
if not data["hasMore"]:
break
offset += 100
return all_catalogs
Available categories
| Slug | Display name |
|---|---|
apparel | Apparel & Fashion |
electronics | Electronics & Tech |
home-goods | Home & Living |
beauty | Beauty & Personal Care |
sports | Sports & Outdoors |
food-beverage | Food & Beverage |
office | Office & B2B Supplies |
automotive | Automotive |
toys | Toys & Games |
health | Health & Wellness |
Error responses
| HTTP status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid query parameter value |
| 429 | RATE_LIMIT_EXCEEDED | Slow down — 60 requests/minute for public endpoint |