Documentación Optimizada para LLM

Copia o descarga esta referencia para pegarla en el contexto de tu asistente de IA para ayuda con la integración de la API de Skakio.

Ver Original

URLs Rápidas: /llms.txt /llms.md

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

ItemValue
Base URLhttps://api.skakio.com
AuthAPI Key → JWT Token
Token TTL15 minutes (configurable 1-60 min)
Response FormatJSON with { code, status, data } wrapper

API Key Types

PrefixTypeUsePermissions
pk_live_PublicClient-side (browsers, mobile)Read-only
sk_live_SecretServer-side onlyFull access
pk_test_Test PublicDevelopmentRead-only, test data
sk_test_Test SecretDevelopmentFull access, test data

Rate Limits

All /api/* endpoints require authentication. Rate limits vary by key type:

Key TypePer MinutePer Day
Public (pk_)10010,000
Secret (sk_)1,000100,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)

EndpointDescription
GET /api/storeGet store details
GET /api/store-listingsGet store’s listings (categories)
GET /api/payment-methodsGet store’s payment methods
GET /api/listing/:idGet listing details
GET /api/listing/:id/publicationsGet products in a listing (with featured/others split)
GET /api/listingsList all listings
GET /api/publication/:idGet product details
GET /api/publicationsList all publications
GET /api/publication/:id/relatedGet related products
GET /api/publication/:id/reviewsGet product reviews
GET /api/media/:idGet media file
GET /api/store/search?store_id=XSearch store products

Write Operations (Secret Keys Only)

EndpointDescription
POST /api/publicationCreate publication
PATCH /api/publication/:idUpdate publication
DELETE /api/publication/:idDelete publication
POST /api/listingCreate listing
PATCH /api/listing/:idUpdate listing
DELETE /api/listing/:idDelete listing
POST /api/mediaUpload media (10MB limit)
DELETE /api/media/:idDelete media

Query Parameters

ParamTypeDescription
expandstringInclude related data: publication, listing, media
pagenumberPage number (default: 1)
limitnumberItems per page (default: 20, max: 50)
sortstringSort field: created_at, price, name
orderstringSort 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

Error Response Format

{
  "code": 401,
  "status": "error",
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "Token has expired"
  }
}

Common Error Codes

HTTPCodeMeaningAction
401TOKEN_EXPIREDJWT expiredRefresh token
401INVALID_API_KEYBad API keyCheck key in Studio
403STORE_ACCESS_DENIEDNo store accessCheck key scope
404NOT_FOUNDResource missingVerify ID
429RATE_LIMIT_EXCEEDEDToo many requestsWait 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

  1. Get API Key - Go to /studio/api-keys and create a key
  2. Store Key Securely - Use environment variables, never commit to git
  3. Exchange for Token - POST to /auth/token with X-API-Key header
  4. Cache Token - Reuse until expiry, refresh proactively
  5. Handle 401 - Auto-refresh token on expiry
  6. Handle 429 - Implement backoff and respect Retry-After
  7. 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"

  • API Keys: /studio/api-keys
  • Interactive Reference: /docs/api
  • Rate Limits: /docs/api/rate-limits
  • Error Codes: /docs/api/errors