The Companion API provides the building blocks for personal shopping agents. It is vertical-agnostic — the Beauty Companion uses it for skincare, but the same endpoints power agents for any product domain.
Looking for the conversational AI agent? See Conversational Agent for the chat and streaming endpoints that combine these primitives into a full AI shopping companion with tool-use, memory, and proactive nudges.
Base Path
All endpoints are prefixed with /api/v1/companion.
Intent Profiles
An intent profile stores what a user wants — their preferences, constraints, and behavioral signals. Agents read and write profiles to personalize recommendations and execute purchases on a user’s behalf.
Create a Profile
curl -X POST https://api.podium.build/api/v1/companion/profile/{userId} \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"skinType": "Sensitive",
"concerns": ["Anti-aging", "Hydration"],
"priceRange": { "min": 75, "max": 150 },
"brands": ["La Mer", "Drunk Elephant"],
"avoidances": ["fragrance", "parabens"]
}'
Response:
{
"id": "clx9abc123def456",
"userId": "clx7user789",
"skinType": "Sensitive",
"concerns": ["Anti-aging", "Hydration"],
"priceRange": { "min": 75, "max": 150 },
"brands": ["La Mer", "Drunk Elephant"],
"avoidances": ["fragrance", "parabens"],
"purchaseHistory": [],
"podiumPoints": 0,
"enrichmentVec": [],
"quizCompletedAt": null,
"createdAt": "2026-03-07T10:00:00Z",
"updatedAt": "2026-03-07T10:00:00Z"
}
Get a Profile
curl https://api.podium.build/api/v1/companion/profile/{userId} \
-H "Authorization: Bearer YOUR_API_KEY"
Update a Profile (Partial)
PATCH merges fields into the existing profile. Omitted fields are unchanged.
curl -X PATCH https://api.podium.build/api/v1/companion/profile/{userId} \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concerns": ["Anti-aging", "Hydration", "Dark spots"],
"priceRange": { "min": 50, "max": 200 }
}'
Award Points
Points are stored on the intent profile and can be used for gamification, tier gating, or reward eligibility.
curl -X POST https://api.podium.build/api/v1/companion/profile/{userId}/points \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 25,
"details": { "reason": "quiz_completed", "quizId": "onboarding_v2" }
}'
Endpoints
| Method | Path | Description |
|---|
GET | /companion/profile/{userId} | Get user’s intent profile |
POST | /companion/profile/{userId} | Create or replace intent profile |
PATCH | /companion/profile/{userId} | Partial update (merge fields) |
POST | /companion/profile/{userId}/points | Award points with metadata |
Profile Schema
| Field | Type | Description |
|---|
id | string | CUID2 identifier |
userId | string | CUID2 — the Podium user this profile belongs to |
skinType | string? | Free-text preference dimension (use whatever fits your vertical) |
concerns | string[] | Array of preference tags |
priceRange | object? | { min: number, max: number } — budget constraints |
brands | string[] | Preferred brands |
avoidances | string[] | Things to exclude (ingredients, materials, etc.) |
purchaseHistory | json[] | Server-managed purchase records |
podiumPoints | integer | Current point balance |
enrichmentVec | float[] | Embedding vector for similarity matching (populated server-side) |
quizCompletedAt | datetime? | When the user completed an onboarding quiz |
Profile fields like skinType, concerns, and avoidances are flexible string fields — they work for any product domain. A fashion agent might use bodyType, stylePreferences, and fabricAvoidances with the same schema structure.
Products
The Companion has its own product catalog (ProductCatalogItem) separate from the main commerce Product model. This lets agents curate a focused catalog from external sources without requiring products to exist in the core Podium commerce system.
List Products
curl "https://api.podium.build/api/v1/companion/products?category=moisturizer&minPrice=20&maxPrice=100&limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
Query Parameters:
| Parameter | Type | Default | Description |
|---|
category | string | — | Filter by category |
brand | string | — | Filter by brand |
minPrice | number | — | Minimum price |
maxPrice | number | — | Maximum price |
inStock | boolean | — | Filter by availability |
search | string | — | Full-text search across name, brand, category |
limit | number | 20 | Results per page |
offset | number | 0 | Pagination offset |
Response:
[
{
"id": "clx9prod001",
"name": "Hydrating Serum with Hyaluronic Acid",
"brand": "The Ordinary",
"category": "serum",
"price": 12.90,
"currency": "USD",
"imageUrl": "https://cdn.example.com/products/serum.jpg",
"productUrl": "https://theordinary.com/products/ha-serum",
"openGraphData": { "title": "...", "description": "..." },
"inStock": true,
"createdAt": "2026-03-01T00:00:00Z"
}
]
Get Product by ID
curl https://api.podium.build/api/v1/companion/products/{productId} \
-H "Authorization: Bearer YOUR_API_KEY"
Create a Product
Agents or backend services can add products to the companion catalog:
curl -X POST https://api.podium.build/api/v1/companion/products \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CeraVe Moisturizing Cream",
"brand": "CeraVe",
"category": "moisturizer",
"price": 18.99,
"imageUrl": "https://cdn.example.com/cerave-cream.jpg",
"productUrl": "https://cerave.com/moisturizing-cream"
}'
Batch Create Products
Add up to 100 products in a single request:
curl -X POST https://api.podium.build/api/v1/companion/products/batch \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "CeraVe Moisturizing Cream",
"brand": "CeraVe",
"category": "moisturizer",
"price": 18.99,
"productUrl": "https://cerave.com/moisturizing-cream"
},
{
"name": "La Roche-Posay Toleriane",
"brand": "La Roche-Posay",
"category": "cleanser",
"price": 15.99,
"productUrl": "https://laroche-posay.com/toleriane"
}
]
}'
Product Schema
| Field | Type | Required | Description |
|---|
name | string | Yes | Product name (max 500 chars) |
brand | string | Yes | Brand name |
category | string | Yes | Product category |
price | number | Yes | Price as a decimal (e.g., 18.99) |
currency | string | No | ISO currency code (default: USD) |
imageUrl | string | No | Product image URL |
productUrl | string | Yes | Canonical product page URL |
openGraphData | object | No | OpenGraph metadata from the product page |
inStock | boolean | No | Availability (default: true) |
Endpoints
| Method | Path | Description |
|---|
GET | /companion/products | List products (filterable, paginated) |
GET | /companion/products/{productId} | Get single product |
POST | /companion/products | Create one product |
POST | /companion/products/batch | Batch create (1–100 items) |
Interactions
Interactions record how a user engages with products. They power the recommendation engine and form the behavioral signal layer of the intent profile.
Record an Interaction
curl -X POST https://api.podium.build/api/v1/companion/interactions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": "clx7user789",
"productId": "clx9prod001",
"action": "RANK_UP",
"score": 0.9
}'
Get User Interactions
curl https://api.podium.build/api/v1/companion/interactions/{userId} \
-H "Authorization: Bearer YOUR_API_KEY"
Interaction Types
| Action | Meaning | Signal Strength |
|---|
RANK_UP | User likes/loves the product | Strong positive |
RANK_DOWN | User dislikes the product | Strong negative |
SKIP | User skipped (neutral) | Weak negative |
PURCHASED | User completed a purchase | Strongest positive |
PURCHASE_INTENT | User started but didn’t complete | Moderate positive |
NUDGE_OPENED | User opened a proactive notification | Weak positive |
Interaction Schema
| Field | Type | Required | Description |
|---|
userId | string | Yes | The user performing the action |
productId | string | Yes | The product being acted on |
action | enum | Yes | One of the six interaction types above |
score | number | No | Optional 0–1 confidence score |
Endpoints
| Method | Path | Description |
|---|
POST | /companion/interactions | Record an interaction |
GET | /companion/interactions/{userId} | Get all interactions for a user |
Recommendations
The recommendation engine uses AI-powered ranking to score products based on a user’s intent profile and interaction history. It returns products the user hasn’t interacted with, scored by relevance to their declared preferences and behavioral signals.
Get Recommendations
curl "https://api.podium.build/api/v1/companion/recommendations/{userId}?count=5&category=serum" \
-H "Authorization: Bearer YOUR_API_KEY"
Query Parameters:
| Parameter | Type | Default | Description |
|---|
count | number | 5 | Number of recommendations to return |
category | string | — | Optional category filter |
Response:
[
{
"id": "clx9prod042",
"name": "Drunk Elephant Protini Polypeptide Cream",
"brand": "Drunk Elephant",
"category": "moisturizer",
"price": 68.00,
"imageUrl": "https://cdn.example.com/de-protini.jpg",
"productUrl": "https://drunkelephant.com/protini",
"inStock": true
}
]
Recommendations exclude products the user has already interacted with (any action type). The engine considers RANK_UP and PURCHASED interactions as positive signals for similar products, and RANK_DOWN as negative signals for similar attributes.
Endpoints
| Method | Path | Description |
|---|
GET | /companion/recommendations/{userId} | AI-ranked product recommendations |
Orders
Companion orders use a concierge fulfillment model — the user pays Podium (via x402 USDC or Stripe), and the platform handles purchasing from the retailer and shipping to the user. This enables agents to execute purchases from any catalog source, not just products that exist in Podium’s core commerce system.
The conversational agent’s create_order tool uses these endpoints automatically during chat — see Conversational Agent for the full flow.
Create a Concierge Order
curl -X POST https://api.podium.build/api/v1/companion/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": "clx7user789",
"productId": "clx9prod042",
"shippingAddress": {
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94102",
"country": "US"
},
"email": "user@example.com"
}'
Response:
{
"id": "clx9order001",
"userId": "clx7user789",
"productId": "clx9prod042",
"status": "PENDING_PAYMENT",
"shippingAddress": {
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94102",
"country": "US"
},
"email": "user@example.com",
"productSnapshot": {
"name": "Drunk Elephant Protini Polypeptide Cream",
"brand": "Drunk Elephant",
"price": 68.00,
"imageUrl": "https://cdn.example.com/de-protini.jpg"
},
"amountUsdc": "68.00",
"createdAt": "2026-03-07T12:00:00Z"
}
List User Orders
curl https://api.podium.build/api/v1/companion/orders/{userId} \
-H "Authorization: Bearer YOUR_API_KEY"
Get Order Detail
curl https://api.podium.build/api/v1/companion/orders/detail/{orderId} \
-H "Authorization: Bearer YOUR_API_KEY"
Update Order Status
curl -X PATCH https://api.podium.build/api/v1/companion/orders/{orderId}/status \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "SHIPPED",
"fulfillmentNotes": "USPS tracking: 9400111899223456789012"
}'
Order Status Flow
| Status | Description |
|---|
PENDING_PAYMENT | Order created, awaiting payment via x402 USDC or Stripe |
PAID | Payment confirmed |
FULFILLING | Platform purchasing from retailer |
SHIPPED | Shipped to user |
DELIVERED | Delivery confirmed |
CANCELLED | Order cancelled |
REFUNDED | Payment refunded |
Order Schema
| Field | Type | Description |
|---|
id | string | CUID2 identifier |
userId | string | The user who placed the order |
productId | string | Companion catalog product |
status | enum | Current order status |
shippingAddress | object | { street, city, state, zip, country } |
email | string | Notification email |
productSnapshot | object | Frozen product data at time of order |
amountUsdc | string | Order amount in USDC (string for precision) |
fulfillmentNotes | string? | Tracking numbers, notes |
podiumOrderId | string? | Linked core Podium order (if bridged) |
Endpoints
| Method | Path | Description |
|---|
POST | /companion/orders | Create a concierge order |
GET | /companion/orders/{userId} | List user’s orders (newest first) |
GET | /companion/orders/detail/{orderId} | Order detail with product |
PATCH | /companion/orders/{orderId}/status | Update status and fulfillment notes |
Chat History
Retrieve durable conversation history for a user. Messages include role, content, timestamps, and any product cards that were shown during the conversation.
Get Chat History
curl "https://api.podium.build/api/v1/companion/agent/chat/history/{userId}?limit=50" \
-H "Authorization: Bearer YOUR_API_KEY"
Query Parameters:
| Parameter | Type | Default | Description |
|---|
limit | number | 50 | Number of messages to return |
before | string | — | Message ID cursor for pagination (returns messages older than this ID) |
Response:
[
{
"id": "msg_clxyz001",
"role": "user",
"content": "What's a good moisturizer for sensitive skin under $50?",
"timestamp": "2026-03-07T14:30:00Z"
},
{
"id": "msg_clxyz002",
"role": "assistant",
"content": "Based on your profile, I'd recommend the CeraVe Moisturizing Cream...",
"timestamp": "2026-03-07T14:30:02Z",
"products": [
{
"id": "clx9prod001",
"name": "CeraVe Moisturizing Cream",
"brand": "CeraVe",
"price": 18.99,
"imageUrl": "https://cdn.example.com/cerave-cream.jpg"
}
]
}
]
Message Schema
| Field | Type | Description |
|---|
id | string | Unique message identifier (use as before cursor for pagination) |
role | string | user or assistant |
content | string | Message text |
timestamp | string | ISO 8601 timestamp |
products | object[]? | Product cards shown with this message (assistant messages only) |
Endpoints
| Method | Path | Description |
|---|
GET | /companion/agent/chat/history/{userId} | Paginated chat history |
Pagination is cursor-based. Pass the id of the oldest message in your current page as the before parameter to fetch the next page. An empty array indicates no more history.
Creator Products
Retrieve the resolved product catalog for a creator persona. Creator personas are profiles that curate products based on a creator’s taste, platform affiliation, and audience — used by apps like Clone Agents (Familiar).
Get Creator Products
curl https://api.podium.build/api/v1/companion/creator/{handle}/products \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
{
"items": [
{
"id": "clx9prod042",
"name": "Drunk Elephant Protini Polypeptide Cream",
"brand": "Drunk Elephant",
"category": "moisturizer",
"price": 68.00,
"imageUrl": "https://cdn.example.com/de-protini.jpg",
"productUrl": "https://drunkelephant.com/protini",
"inStock": true
}
],
"creatorHandle": "janedoe",
"displayName": "Jane Doe"
}
| Field | Type | Description |
|---|
items | object[] | Array of resolved product catalog items |
creatorHandle | string | The creator’s unique handle |
displayName | string | The creator’s display name |
Endpoints
| Method | Path | Description |
|---|
GET | /companion/creator/{handle}/products | Get a creator’s curated product catalog |
Profile Seeding from Creator
Merge a creator’s taste profile — preferred brands, price ranges, product concerns — into a user’s intent profile. This lets users bootstrap their preferences from a creator they trust, without going through a full onboarding quiz.
Seed Profile from Creator
curl -X POST https://api.podium.build/api/v1/companion/profile/{userId}/seed-from-creator \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"creatorHandle": "janedoe"
}'
Behavior:
- Merges the creator’s brand preferences, price range, and concern tags into the user’s intent profile
- Does not overwrite user-set fields if the user has completed an onboarding quiz (i.e.,
quizCompletedAt is set)
- Safe to call multiple times — subsequent calls merge additively
Endpoints
| Method | Path | Description |
|---|
POST | /companion/profile/{userId}/seed-from-creator | Seed user profile from creator taste data |
Subscription Endpoints
The subscription system handles billing, usage tracking, and tier management for companion agents. For full documentation — including tiers, usage gating, memory gating, and integration examples — see Subscriptions.
| Method | Path | Description |
|---|
POST | /companion/subscription/checkout | Create a Stripe checkout session |
POST | /companion/subscription/portal | Open Stripe customer portal |
GET | /companion/subscription/{userId}/status | Get subscription status and usage |
User Linking
Connect external platform users (e.g., Telegram) to Podium users. The companion looks up users by a synthetic email pattern.
curl "https://api.podium.build/api/v1/companion/user/by-telegram/{telegramId}?privyId=did:privy:abc123" \
-H "Authorization: Bearer YOUR_API_KEY"
| Parameter | Type | Description |
|---|
telegramId | string (path) | Telegram user ID |
privyId | string (query, optional) | Privy DID to link to the user |
The lookup uses a synthetic email pattern: tg_{telegramId}@beauty-companion.podium.app. If a matching user exists, it’s returned. If privyId is provided, the Privy DID is linked to the user record.
Building an Agent: End-to-End Flow
Here’s the typical lifecycle of a companion agent interaction:
Onboard the User
Create an intent profile with the user’s stated preferences. This can come from a quiz, conversation, or imported data.import { createPodiumClient } from '@podium-sdk/node-sdk'
const client = createPodiumClient({ apiKey: process.env.PODIUM_API_KEY })
const { data: profile } = await client.companion.createProfile({
userId,
requestBody: {
concerns: ["Hydration", "Anti-aging"],
priceRange: { min: 30, max: 100 },
avoidances: ["fragrance"],
},
})
Get Recommendations
The engine uses the profile + interaction history to rank products with AI-powered relevance scoring.const { data: recs } = await client.companion.listRecommendations({
userId,
count: 5,
})
Present and Record Feedback
Show recommendations to the user. Record their reactions as interactions.await client.companion.createInteractions({
requestBody: {
userId,
productId: recs[0].id,
action: "RANK_UP",
},
})
Execute Purchase
When the user approves, create a concierge order. Payment can be processed via x402 (USDC) or linked to a Stripe checkout.const { data: order } = await client.companion.createOrders({
requestBody: {
userId,
productId: recs[0].id,
shippingAddress: userAddress,
email: userEmail,
},
})
Refine Over Time
Each interaction improves future recommendations. The enrichmentVec on the profile is updated server-side as the user’s pattern emerges. Award points for engagement to drive retention.await client.companion.createProfilePoints({
userId,
requestBody: {
amount: 10,
details: { reason: "feedback_given" },
},
})