Skakio Catalog API - LLM Quick Reference
For AI Assistants: This document provides everything needed to help developers integrate with the Skakio Catalog API. Copy code snippets directly - they are production-ready.
TL;DR - Minimal Working Example
// 1. Get token (exchange API key for JWT)
const tokenRes = await fetch('https://api.skakio.com/auth/token', {
method: 'POST',
headers: { 'X-API-Key': 'pk_live_YOUR_KEY' }
});
const { data: { token } } = await tokenRes.json();
// 2. Make API calls
const storeRes = await fetch('https://api.skakio.com/api/store', {
headers: { 'Authorization': `Bearer ${token}` }
});
const { data: store } = await storeRes.json();
API Overview
| Item | Value |
|---|
| Base URL | https://api.skakio.com |
| Auth | API Key → JWT Token |
| Token TTL | 15 minutes (configurable 1-60 min) |
| Response Format | JSON with { code, status, data } wrapper |
API Key Types
| Prefix | Type | Use | Permissions |
|---|
pk_live_ | Public | Client-side (browsers, mobile) | Read-only |
sk_live_ | Secret | Server-side only | Full access |
pk_test_ | Test Public | Development | Read-only, test data |
sk_test_ | Test Secret | Development | Full access, test data |
Rate Limits
All /api/* endpoints require authentication. Rate limits vary by key type:
| Key Type | Per Minute | Per Day |
|---|
| Public (pk_) | 100 | 10,000 |
| Secret (sk_) | 1,000 | 100,000 |
Authentication Flow
┌─────────────────────────────────────────────────────────────┐
│ POST /auth/token → Get JWT token (15 min TTL) │
│ POST /auth/refresh → Refresh existing token │
│ POST /auth/validate → Check token validity │
└─────────────────────────────────────────────────────────────┘
Get Token
curl -X POST https://api.skakio.com/auth/token \
-H "X-API-Key: pk_live_your_key"
Response:
{
"code": 200,
"status": "OK",
"data": {
"token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900
}
}
Custom Token TTL
curl -X POST https://api.skakio.com/auth/token \
-H "X-API-Key: pk_live_your_key" \
-H "Content-Type: application/json" \
-d '{"ttl_minutes": 30}'
Refresh Token
curl -X POST https://api.skakio.com/auth/refresh \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
Core Endpoints
Read Operations (Public + Secret Keys)
| Endpoint | Description |
|---|
GET /api/store | Get store details |
GET /api/store-listings | Get store’s listings (categories) |
GET /api/payment-methods | Get store’s payment methods |
GET /api/listing/:id | Get listing details |
GET /api/listing/:id/publications | Get products in a listing (with featured/others split) |
GET /api/listings | List all listings |
GET /api/publication/:id | Get product details |
GET /api/publications | List all publications |
GET /api/publication/:id/related | Get related products |
GET /api/publication/:id/reviews | Get product reviews |
GET /api/media/:id | Get media file |
GET /api/store/search?store_id=X | Search store products |
Write Operations (Secret Keys Only)
| Endpoint | Description |
|---|
POST /api/publication | Create publication |
PATCH /api/publication/:id | Update publication |
DELETE /api/publication/:id | Delete publication |
POST /api/listing | Create listing |
PATCH /api/listing/:id | Update listing |
DELETE /api/listing/:id | Delete listing |
POST /api/media | Upload media (10MB limit) |
DELETE /api/media/:id | Delete media |
Query Parameters
| Param | Type | Description |
|---|
expand | string | Include related data: publication, listing, media |
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20, max: 50) |
sort | string | Sort field: created_at, price, name |
order | string | Sort order: asc, desc |
Example: Get Store with Listings
curl "https://api.skakio.com/api/store-listings?expand=listing" \
-H "Authorization: Bearer TOKEN"
Example: Get Listing with Products
curl "https://api.skakio.com/api/listing/list_xyz?expand=publication&limit=20" \
-H "Authorization: Bearer TOKEN"
Example: Search Products
curl "https://api.skakio.com/api/store/search?store_id=store_abc123&q=sneakers&limit=10" \
-H "Authorization: Bearer TOKEN"
Response Structures
Store
interface Store {
id: string;
object: string; // "store"
name: string;
status: string;
description?: string;
businessHours?: { day: string; hours: string }[];
contact?: {
address?: string;
phone?: string;
email?: string;
};
policies?: {
shipping?: string;
returns?: string;
satisfaction?: string;
};
ruc?: string;
created_at: string;
updated_at: string;
stats?: { listing_count: number };
listings?: ListingCollection; // When expanded
media?: MediaCollection; // When expanded
}
Listing (Category)
interface Listing {
id: string;
object: string; // "listing"
name: string;
visibility: string;
description?: string;
default_sort_mode: string;
created_at: string;
updated_at: string;
stats?: {
media_count: number;
publication_count: number;
};
publications?: PublicationCollection; // When expanded
media?: MediaCollection; // When expanded
}
Publication (Product)
interface Publication {
id: string;
object: string; // "publication"
title: string;
description: string;
description_html?: string;
visibility: string;
price_currency: string; // "PEN", "USD", etc.
price_amount: number; // Integer in cents (e.g., 2999 = 29.99)
price_active: boolean;
free_shipping?: boolean;
quantity_available?: number;
featured_position?: number;
created_at: string;
updated_at: string;
stats?: {
media_count: number;
view_count: number;
};
listing?: Listing;
media?: MediaCollection;
}
Listing Publications Response
The GET /api/listing/:id/publications endpoint returns a split of featured and non-featured products:
interface ListingPublicationsResponse {
featured: Publication[];
others: Publication[];
meta: {
default_sort_mode: string;
effective_sort_mode: string;
pagination: {
page: number;
limit: number;
total: number;
total_pages: number;
};
};
}
Paginated Response
interface PaginatedResponse<T> {
code: number;
status: string;
data: T[];
meta: {
page: number;
take: number;
item_count: number;
pagination: {
page: number;
total_pages: number;
total_items: number;
limit: number;
};
};
}
Production-Ready Client Class
const API_BASE = 'https://api.skakio.com';
class SkakioClient {
private apiKey: string;
private token: string | null = null;
private tokenExpiresAt = 0;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
private async getToken(): Promise<string> {
const now = Date.now();
const buffer = 60_000; // 1 minute buffer
if (!this.token || now > this.tokenExpiresAt - buffer) {
const res = await fetch(`${API_BASE}/auth/token`, {
method: 'POST',
headers: { 'X-API-Key': this.apiKey },
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.error?.message || 'Failed to get token');
}
const { data } = await res.json();
this.token = data.token;
this.tokenExpiresAt = now + data.expires_in * 1000;
}
return this.token!;
}
async fetch<T>(path: string): Promise<T> {
const token = await this.getToken();
const res = await fetch(`${API_BASE}${path}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (res.status === 401) {
// Token expired, force refresh
this.token = null;
return this.fetch(path);
}
if (res.status === 429) {
const retryAfter = res.headers.get('Retry-After') || '60';
throw new Error(`Rate limited. Retry after ${retryAfter}s`);
}
if (!res.ok) {
const error = await res.json();
throw new Error(error.error?.message || `API error: ${res.status}`);
}
const { data } = await res.json();
return data;
}
}
// Usage
const api = new SkakioClient('pk_live_your_key');
const store = await api.fetch<Store>('/api/store');
const listings = await api.fetch<Listing[]>('/api/store-listings');
const products = await api.fetch<Publication[]>('/api/listing/list_xyz?expand=publication');
Framework Integration Patterns
Next.js (App Router)
// lib/skakio.ts
const api = new SkakioClient(process.env.SKAKIO_API_KEY!);
// app/page.tsx
export default async function Home() {
const store = await api.fetch<Store>('/api/store');
return <h1>{store.name}</h1>;
}
Astro
---
// src/pages/index.astro
const api = new SkakioClient(import.meta.env.SKAKIO_API_KEY);
const store = await api.fetch('/api/store');
---
<h1>{store.name}</h1>
React (Client-Side)
// Use public key only in client-side code
const api = new SkakioClient('pk_live_xxx');
function ProductList({ listingId }: { listingId: string }) {
const [products, setProducts] = useState<Publication[]>([]);
useEffect(() => {
api.fetch<Listing>(`/api/listing/${listingId}?expand=publication`)
.then(listing => setProducts(listing.publications || []));
}, [listingId]);
return products.map(p => <div key={p.id}>{p.title}</div>);
}
Error Handling
{
"code": 401,
"status": "error",
"error": {
"code": "TOKEN_EXPIRED",
"message": "Token has expired"
}
}
Common Error Codes
| HTTP | Code | Meaning | Action |
|---|
| 401 | TOKEN_EXPIRED | JWT expired | Refresh token |
| 401 | INVALID_API_KEY | Bad API key | Check key in Studio |
| 403 | STORE_ACCESS_DENIED | No store access | Check key scope |
| 404 | NOT_FOUND | Resource missing | Verify ID |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests | Wait and retry |
Error Handling Pattern
try {
const data = await api.fetch('/api/store');
} catch (err) {
if (err.message.includes('Rate limited')) {
// Wait and retry
} else if (err.message.includes('TOKEN_EXPIRED')) {
// Token auto-refreshed, retry
} else {
// Handle other errors
console.error(err);
}
}
Environment Variables
# .env.local (Next.js) or .env (other)
SKAKIO_API_KEY=pk_live_your_public_key_here
# For server-side only operations
SKAKIO_SECRET_KEY=sk_live_your_secret_key_here
Quick Checklist for Integration
- Get API Key - Go to
/studio/api-keys and create a key
- Store Key Securely - Use environment variables, never commit to git
- Exchange for Token - POST to
/auth/token with X-API-Key header
- Cache Token - Reuse until expiry, refresh proactively
- Handle 401 - Auto-refresh token on expiry
- Handle 429 - Implement backoff and respect
Retry-After
- Use Expand - Reduce requests by expanding related data
Common Use Cases
Build a Storefront
// 1. Get store info
const store = await api.fetch('/api/store');
// 2. Get categories (listings)
const listings = await api.fetch('/api/store-listings');
// 3. Get products in a category
const listing = await api.fetch(`/api/listing/${listingId}?expand=publication`);
// 4. Get single product
const product = await api.fetch(`/api/publication/${productId}`);
Search Products
const results = await api.fetch(
`/api/store/search?store_id=${storeId}&q=${encodeURIComponent(query)}&limit=20`
);
Paginate Results
async function* getAllProducts(listingId: string) {
let page = 1;
let hasMore = true;
while (hasMore) {
const res = await api.fetch(
`/api/listing/${listingId}/publications?page=${page}&limit=50`
);
yield* res.others;
hasMore = page < res.meta.pagination.total_pages;
page++;
}
}
Price Handling
Prices are returned in cents (integer). Convert to display:
// price_amount is in cents: 2999 = S/ 29.99
const displayPrice = (amount: number, currency: string) =>
`${currency} ${(amount / 100).toFixed(2)}`;
// Example: displayPrice(2999, "PEN") → "PEN 29.99"
Links
- API Keys:
/studio/api-keys
- Interactive Reference:
/docs/api
- Rate Limits:
/docs/api/rate-limits
- Error Codes:
/docs/api/errors