# Avileo public catalog integration
Use this context only for browser-facing storefronts with an avileo_pk_ Client Key.
# Conectar un storefront
URL: https://avileo.theelena.me/docs/cloud-storefront
Description: Conecta una tienda web externa a Avileo Cloud con el SDK público.
# Conectar un storefront a Avileo Cloud
Avileo puede ser la fuente de verdad de una tienda web externa. Tu negocio
administra el catálogo y los pedidos en Avileo; tu storefront consume esa
información desde el navegador o desde un servidor SSR mediante el SDK público.
## Qué puedes construir
- un catálogo web propio para un negocio Avileo;
- una tienda móvil con categorías, productos y stock público;
- un carrito de invitado y checkout;
- una experiencia con React, TypeScript o HTTP directo;
- un storefront desplegado en Cloudflare Workers u otro proveedor compatible.
El storefront queda vinculado a un solo negocio mediante su Client Key. No
necesitas enviar un `businessId` ni permitir que el visitante cambie de negocio.
## 1. Prepara la conexión
Necesitas:
1. un negocio Avileo con catálogo público habilitado;
2. el origen de la API pública de Avileo;
3. una Client Key con formato `avileo_pk_...`.
La Client Key es publicable y puede llegar al navegador. Identifica un solo
negocio y solo permite usar capacidades públicas: perfil, catálogo, carrito,
pedidos y pagos compartibles según la configuración del negocio.
No uses credenciales administrativas ni claves de agentes en un storefront.
## 2. Instala el SDK
```sh
npm install @tarileo/avileo-sdk@0.3.0 @tanstack/react-query
```
Configura la conexión en variables públicas del storefront:
```env
VITE_AVILEO_API_URL=https://avileo.theelena.me
VITE_AVILEO_CLIENT_KEY=avileo_pk_...
```
En desarrollo, usa el backend local o Tailscale de Avileo de forma explícita.
No permitas que el entorno local apunte silenciosamente a producción.
## 3. Crea el cliente
```ts
import { createAvileo } from '@tarileo/avileo-sdk';
const avileo = createAvileo({
baseUrl: import.meta.env.VITE_AVILEO_API_URL,
clientKey: import.meta.env.VITE_AVILEO_CLIENT_KEY,
});
const store = avileo.business();
const profile = await store.getInfo();
const catalog = await store.catalog.list();
```
El SDK envía automáticamente `x-avileo-client-key` y mantiene el negocio
resuelto por esa clave. No construyas una segunda selección de tenant en el
frontend.
## 4. Con React
Monta un solo provider en la raíz de tu storefront y reutiliza el mismo
`QueryClient`:
```tsx
import type { QueryClient } from '@tanstack/react-query';
import { AvileoProvider, useBusiness, useCatalog, useCart } from '@tarileo/avileo-sdk/react';
export function App({ queryClient }: { queryClient: QueryClient }) {
return (
);
}
function Storefront() {
const business = useBusiness();
const catalog = useCatalog();
const cart = useCart();
// Render business.data, catalog.data and cart.cart in the storefront.
return null;
}
```
Usa `useBusiness`, `useCatalog`, `useCart` y `useCheckout` para mantener perfil,
catálogo, carrito y compra dentro del contrato público del SDK.
## 5. Catálogo, carrito y checkout
```tsx
const catalog = useCatalog({
search: search.trim() || undefined,
categoryId: selectedCategoryId === 'all' ? undefined : selectedCategoryId,
});
const cart = useCart();
await cart.addItem({
productId: product.id,
variantId: variant.id,
quantity: 1,
});
```
Los badges, líneas y totales del carrito deben salir de `cart.cart`. Para
confirmar el pedido:
```ts
const instance = cart.cartInstance;
if (instance) {
await instance.checkout({
customerName,
customerPhone,
});
cart.clear();
}
```
Consulta [SDK Core](/docs/sdk-core), [React](/docs/react) y
[Examples](/docs/examples) para el contrato completo y el manejo de estados
vacíos, carga y errores.
## 6. Despliega tu storefront
El hosting del storefront es independiente de Avileo Cloud. Puedes desplegarlo
en Cloudflare Workers u otro proveedor compatible con tu arquitectura SSR.
En producción:
- configura `VITE_AVILEO_API_URL` con el origen público correcto;
- inyecta `VITE_AVILEO_CLIENT_KEY` durante el build o runtime público;
- verifica que SSR y navegador resuelvan el mismo negocio;
- prueba una lectura de perfil y catálogo antes de publicar;
- no subas claves privadas ni secretos de administración al bundle.
Puedes revisar una implementación de referencia en
`apps/stuff/elite-coffee`, que usa TanStack Start, React Query, el SDK de Avileo
y Cloudflare Workers.
## Si no quieres usar React
El SDK Core funciona con TypeScript, JavaScript, Node y runtimes edge. Para una
integración sin dependencia del SDK, consulta [HTTP](/docs/http) y envía la
Client Key en cada petición pública.
Si necesitas automatizar un catálogo con un agente, sigue la guía de
[Skill para agentes](/docs/agent-skill). Ese flujo es server-side y tiene una
frontera de credenciales distinta al storefront.
---
# Client Key
URL: https://avileo.theelena.me/docs/client-key
Description: Usa avileo_pk_... como identificador público seguro para tu storefront.
# Client Key
The **Client Key** is a public key that identifies an Avileo business. It has this format:
```txt
avileo_pk_xxx...
```
You can place it in frontend code, public storefront environment variables, AI-agent prompts, or internal documentation. It is designed to be visible in the browser.
## What it allows
A Client Key enables public SDK capabilities:
- read the business public profile;
- list the catalog, categories, products, and public stock;
- create and update draft carts;
- look up orders or payments through a shared token;
- query service sessions when the business supports them.
- inspect the business's presentation-safe public image assets for storefront composition;
## What it does not allow
The Client Key **does not** create a dashboard session and **does not** authenticate an administrative user. It does not grant access to internal sales, private settings, full customer records, reports, or dashboard permissions.
If someone opens the Avileo dashboard, they still need to sign in with Better Auth and their normal permissions. The Client Key only resolves the business for public endpoints.
## HTTP header
The SDK automatically sends this header:
```http
x-avileo-client-key: avileo_pk_...
```
If you use direct HTTP, send it on every request to `/api/v1/public/sdk/*`.
## Rotation and revocation
You can create multiple Client Keys per business, for example one per storefront. To rotate a key:
1. generate a new key in the dashboard;
2. update the storefront or AI agent;
3. confirm traffic is using the new key;
4. revoke the old key.
When a key is missing, invalid, or revoked, the backend responds with 401 or an equivalent public error, and the SDK throws a typed error class.
---
# SDK Core
URL: https://avileo.theelena.me/docs/sdk-core
Description: Integra Avileo desde TypeScript o JavaScript sin depender de React.
# SDK Core
The core `@tarileo/avileo-sdk` package works in the browser, Node, edge functions, or any runtime with `fetch`.
## Create the client
```ts
import { createAvileo } from '@tarileo/avileo-sdk';
const avileo = createAvileo({
baseUrl: 'https://avileo.theelena.me',
clientKey: 'avileo_pk_...',
});
```
`createAvileo({ baseUrl, clientKey })` normalizes the base URL and sends `x-avileo-client-key` on every request. It also accepts:
| Field | Type | Usage |
| ----------- | -------------- | --------------------------------------------------- |
| `baseUrl` | `string` | API base URL, with no required trailing slash. |
| `clientKey` | `string` | The business public Client Key. |
| `fetch` | `typeof fetch` | Custom fetch for tests, SSR, or polyfills. |
| `timeoutMs` | `number` | Request timeout implemented with `AbortController`. |
## Business scope
```ts
const store = avileo.business();
const profile = await store.getInfo();
```
`avileo.business()` returns a `BusinessScope` already bound to the Client Key. Do not pass `businessSlug`; the backend resolves the tenant from the key.
## Available resources
### Profile
```ts
const profile = await store.getInfo();
const canUseSessions = await store.has('sessions');
```
`BusinessScope.getInfo()` caches the public profile for the lifetime of the scope.
### Catalog
```ts
const { business, categories, products } = await store.catalog.list({
search: 'chicken',
categoryId: 'cat_123',
});
```
### Assets
Before composing a hero, gallery, or visual section, inspect the tenant's real public images:
```ts
const assets = await store.assets.list({
search: 'fachada',
mimeType: 'image/webp',
limit: 24,
offset: 0,
});
```
`assets.items` contains only active, non-deleted, non-hidden image assets for the Client Key tenant.
Each item exposes `id`, `description`, `mimeType`, optional `width`/`height`, `createdAt`, and an
absolute stable `url` ending in `/api/v1/public/sdk/assets/:id/render`. It never exposes storage paths, signed URLs,
asset metadata, original filenames, or owner data. The response also includes `total`, `limit`, and
`offset` for pagination.
### Sessions
```ts
const settings = await store.sessions.getSettings();
const lookup = await store.sessions.lookup({ itemCode: 'ABC-123' });
const quote = store.sessions.quoteLocal(lookup, new Date());
```
Use sessions only if the business has that public capability.
### Carts
```ts
const cart = await store.carts().create();
await cart.addItem({ productId: 'prod_1', variantId: 'var_1', quantity: 2 });
await cart.checkout({ customerName: 'Jane', customerPhone: '999999999' });
```
`carts().create()` returns a `Cart` with a `token`. The token identifies the draft and lets you rehydrate it:
```ts
const cart = store.carts().fromToken(savedToken);
const sale = await cart.get();
```
### Orders
```ts
const order = await store.orders.get(token);
await store.orders.cancel(token, order.sale.version);
```
### Payments
```ts
const payment = await store.payments.get('paytoken_123');
```
## Direct access
`createAvileo` also exposes `currentBusiness` for callers that prefer a stable property and `leads` for public lead capture.
---
# React
URL: https://avileo.theelena.me/docs/react
Description: Usa AvileoProvider y hooks de React para catálogos y carritos públicos.
# React
`@tarileo/avileo-sdk/react` adds a provider and hooks on top of TanStack Query.
## Provider
Install the provider once at the storefront root:
```tsx
import { AvileoProvider } from '@tarileo/avileo-sdk/react';
export default function StorefrontLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
You can also pass your own `queryClient` if your app already manages one.
## Main hooks
```tsx
import { useBusiness, useCatalog, useCart, useSessionSettings } from '@tarileo/avileo-sdk/react';
export function CatalogPage() {
const { data: business } = useBusiness();
const { data: catalog, isLoading } = useCatalog();
const cart = useCart();
const sessionSettings = useSessionSettings();
if (isLoading) return
);
}
```
### `useBusiness`
Loads `BusinessScope.getInfo()` and returns the business public profile.
### `useCatalog(params?)`
Loads the public catalog. Accepts filters such as `search` and `categoryId`.
Pass `undefined` for empty filters:
```tsx
const params = {
search: search.trim() || undefined,
categoryId: selectedCategoryId === 'all' ? undefined : selectedCategoryId,
};
const catalog = useCatalog(params);
```
Use `category.id` from `catalog.data.categories` for category filters. Do not
filter by category name. Keep the category row visible even when no products
match the current search.
### `useCart`
Keeps the browser draft cart. It persists the token in `localStorage` under a namespace derived from the Client Key so different businesses do not share drafts.
Returns:
- `cart`: the current public sale or `null`;
- `isLoading` and `error`;
- `addItem`, `updateItem`, `removeItem`;
- `clear` to forget the local draft;
- `cartInstance` for advanced operations.
After `await cart.addItem(...)`, render the badge, line count, line items, and
totals from `cart.cart`. Avoid duplicate local counters; they can drift from the
SDK state.
Cart item prices come from sale item fields:
```tsx
const unitPrice = item.unitPrice ?? item.unitPriceQuoted ?? item.unitPriceFinal;
```
Use `item.subtotal` for the line total and `cart.cart.totalAmount` for the cart
total. Do not render a fallback like `S/ 0.00 c/u` when the sale item has a real
subtotal.
### `useSessionSettings`
Loads public session settings when the business supports session-based services.
## Cache by Client Key
The hooks use TanStack Query. The provider creates the `BusinessScope` with the current Client Key, and `useCart` persists the token under `avileo:cart:v2:`, where the namespace comes from the public key. This prevents a multi-business storefront from mixing carts between Client Keys.
---
# Examples
URL: https://avileo.theelena.me/docs/examples
Description: Patrones listos para catálogo, carrito y cancelación de drafts.
# Examples
## Basic catalog + cart
```tsx
import { useCatalog, useCart } from '@tarileo/avileo-sdk/react';
function money(value: string | number | null | undefined) {
return `S/ ${Number(value ?? 0).toFixed(2)}`;
}
export function Storefront({ search, categoryId }: { search: string; categoryId: string }) {
const { data, isLoading } = useCatalog({
search: search.trim() || undefined,
categoryId: categoryId === 'all' ? undefined : categoryId,
});
const cart = useCart();
if (isLoading) return
Loading catalog...
;
return (
<>
{data?.categories.map((category) => (
))}
{data?.products.length === 0 ? (
No products match this search. Clear filters and try again.
);
})}
{money(cart.cart?.totalAmount)}
);
}
```
## Cart checkout
```tsx
import { useCart } from '@tarileo/avileo-sdk/react';
export function CheckoutButton() {
const cart = useCart();
async function checkout() {
const instance = cart.cartInstance;
if (!instance) return;
await instance.checkout({
customerName: 'Jane Doe',
customerPhone: '999999999',
});
cart.clear();
}
return ;
}
```
## Cancel a draft with a stored token
```ts
import { createAvileo } from '@tarileo/avileo-sdk';
const avileo = createAvileo({ baseUrl, clientKey });
const store = avileo.business();
const order = await store.orders.get(token);
if (order.sale.status === 'draft') {
await store.orders.cancel(token, order.sale.version);
}
```
## Direct HTTP for agents
```ts
const response = await fetch(`${baseUrl}/api/v1/public/sdk/catalog`, {
headers: {
'x-avileo-client-key': 'avileo_pk_...',
},
});
const payload = await response.json();
```
---
# Storefront Polish
URL: https://avileo.theelena.me/docs/storefront-polish
Description: Reglas de UX mobile-first para storefronts Avileo generados por IA.
# Storefront Polish
Use this guide when building a public storefront from an Avileo Client Key. The
goal is a fast mobile ordering tool, not a generic generated landing page.
## Mobile-first baseline
- Optimize first at 390x844.
- Use a single-column product list on mobile when cards include variants,
quantity controls, or add-to-cart buttons.
- Touch targets must be at least 44px.
- Text must fit at 390px width without overlapping prices, selectors, or
buttons.
- Keep catalog, cart, and checkout reachable within a few taps.
## Visual polish
- Cards and panels should use radius <= 8px. Pills can be fully rounded.
- Prefer subtle borders and spacing before shadows. Shadows should be minimal
and rare.
- Avoid decorative gradients, glow effects, blobs, nested cards, oversized hero
panels, testimonials, and marketing sections in the buying flow.
- Keep typography compact inside product cards. Reserve large display type for
the page title.
- Product names, prices, variant selects, steppers, and add buttons should scan
quickly.
## Interaction states
- Keep variant select, quantity stepper, price, and add button visually stable
during loading and mutation.
- Disable buttons while the mutation is in flight and restore them after the
SDK state settles.
- Use compact skeletons or inline loading states. Do not block the whole page
for data already available in `cart.cart`.
- If cart or checkout hydration takes more than 3 seconds, keep the UI usable
and show a retry path if the request fails.
## Search and filters
- Call `useCatalog({ search, categoryId })`.
- Pass `undefined` for empty `search` and `categoryId`.
- Filter categories by `category.id`, not category name.
- Keep category controls visible when a search or filter returns zero products.
- Empty states should explain how to recover and include a clear/reset filters
action.
## Cart and checkout
- Derive cart badges, line counts, items, and totals only from `cart.cart`.
- Do not keep duplicate local cart counters.
- Render line unit prices from
`unitPrice ?? unitPriceQuoted ?? unitPriceFinal`.
- Render totals from `subtotal` and `cart.cart.totalAmount`.
- Never show `S/ 0.00 c/u` when a line has a real subtotal or total.
- Checkout should render from the current `cart.cart` when available and show
concise labels for name, phone, address/date, and notes.
## Reject before delivery
Do not mark the storefront done if any of these are true:
- Search or category filters empty products that are known to exist.
- Category controls disappear in an empty state.
- Cart count, line items, or totals disagree with `cart.cart`.
- Unit prices show `S/ 0.00 c/u` while line subtotal or cart total is non-zero.
- Checkout appears stuck on loading.
- Product controls overlap or shift on a 390px mobile viewport.
- The buying flow looks like a marketing landing page instead of an operational
commerce tool.
## Required QA evidence
Before final delivery, verify on mobile 390x844:
- Initial catalog is visible.
- Search returns a known matching product.
- Category filter returns a known matching product.
- Empty search/filter state keeps recovery controls visible.
- Add-to-cart updates badge, line item, and total.
- Cart line item shows correct unit price and subtotal.
- Checkout fields appear with usable loading/error states.
- The confirm button can be reached and becomes enabled after valid input.
---
# Errors
URL: https://avileo.theelena.me/docs/errors
Description: Maneja errores tipados del SDK y recupera conflictos de versión.
# Errors
The SDK converts the backend public envelope into error classes.
| Backend code | SDK error | When it appears |
| ------------------ | ---------------------------- | ------------------------------------------------------ |
| `NOT_FOUND` | `NotFoundError` | Resource, token, or key not found. |
| `VALIDATION_ERROR` | `ValidationError` | Invalid input for cart, checkout, or filters. |
| `CONFLICT` | `ConflictError` | The sale version changed before checkout/cancellation. |
| `FORBIDDEN` | `UnsupportedCapabilityError` | The public capability is not enabled for the business. |
| other | `AvileoError` | Unmapped public error. |
`NetworkError` is thrown when `fetch` fails or the server does not return valid JSON.
## Conflict recovery
```ts
import { ConflictError } from '@tarileo/avileo-sdk';
try {
await cart.checkout({ customerName: 'Jane' });
} catch (err) {
if (err instanceof ConflictError) {
// The sale changed after we loaded it; refresh and show the current state.
const current = await cart.get();
renderUpdatedCart(current);
return;
}
throw err;
}
```
## Customer-facing messages
- `ValidationError`: show the field or rule that must be corrected.
- `NotFoundError`: ask the user to reload the storefront or check the Client Key.
- `UnsupportedCapabilityError`: hide the unavailable feature.
- `NetworkError`: offer a retry.
---
# HTTP
URL: https://avileo.theelena.me/docs/http
Description: Contrato público /api/v1/public/sdk/* para integraciones sin SDK.
# HTTP
You can integrate Avileo without the SDK by calling the public endpoints directly.
## Base path
All public SDK endpoints live under:
```txt
/api/v1/public/sdk/*
```
Examples:
```txt
GET /api/v1/public/sdk/business
GET /api/v1/public/sdk/catalog
GET /api/v1/public/sdk/assets
POST /api/v1/public/sdk/carts
GET /api/v1/public/sdk/orders/:token
GET /api/v1/public/sdk/payments/:token
```
## Required header
Include the Client Key on every request:
```http
x-avileo-client-key: avileo_pk_...
```
The SDK always overwrites any manual `x-avileo-client-key` value with the configured `clientKey` to avoid mixing businesses.
## Envelopes
Successful response:
```json
{
"success": true,
"data": {}
}
```
Error response:
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid quantity"
}
}
```
## 401 without Client Key
If `x-avileo-client-key` is missing, the backend responds with 401. In direct HTTP, treat it as incomplete storefront configuration. In the SDK, it is transformed into a public error class based on the received envelope.
## Best practices
- Do not call administrative endpoints with the Client Key.
- Do not store secrets next to `avileo_pk_...`; the Client Key is public, secrets are not.
- Keep `baseUrl` pointing to the correct API for your environment.
- For AI storefront builders, provide `/llms-catalog.txt` together with the Client Key. Never provide `/llms-agent.txt` or an Agent Key to browser-facing code.
- Before designing a hero or visual section, call `GET /api/v1/public/sdk/assets` with the Client Key
and compose from the returned tenant-scoped images instead of inventing image inventory.