Skip to content
Cresva
Developers

SDKs

Official client libraries for the Agent Commerce Protocol. Install an SDK to start querying storefronts in minutes.

JavaScript / TypeScript

Full-featured SDK with TypeScript types, auto-retry, and streaming support.

@cresva/sdk

Python

Pythonic SDK with type hints, async support, and automatic pagination.

cresva

SDK Comparison

Both SDKs provide full coverage of the Agent Commerce Protocol. Choose the one that matches your runtime.

FeatureJS / TSPython
Typed responsesTypeScript genericsPydantic models
Async supportNative (Promise)AsyncCresvaClient
StreamingYesYes
Auto-paginationYesYes
Auto-retryYesYes
Middleware / hooksYesYes
Batch operationsYesYes
NegotiationYesYes
Trust scoringYesYes
Min runtimeNode 18+Python 3.9+
Package size~42 KB gzipped~38 KB

JavaScript / TypeScript SDK

Installation

The SDK requires Node.js 18 or later. Install it with your preferred package manager.

npm install @cresva/sdk

TypeScript definitions are included in the package -- no separate @types install is needed.

Initialization

TypeScript
import { CresvaClient } from "@cresva/sdk";

const client = new CresvaClient({
  apiKey: process.env.CRESVA_API_KEY,  // pk_live_* or sk_live_*
  brandId: "brand_abc123",
  // Optional configuration:
  timeout: 30000,       // Request timeout in ms (default: 30s)
  maxRetries: 3,        // Auto-retry on transient errors (default: 3)
  baseUrl: undefined,   // Override base URL (for testing)
});

Authentication

The SDK supports two key types. Use publishable keys (pk_live_*) for read-only operations in client-side code, and secret keys (sk_live_*) for write operations on the server side. Never expose secret keys to the browser.

Environment-based auth (recommended)
// The SDK automatically reads CRESVA_API_KEY from process.env
// when no apiKey is passed explicitly.
const client = new CresvaClient({ brandId: "brand_abc123" });

// Explicit key (useful for multi-tenant setups)
const client2 = new CresvaClient({
  apiKey: "sk_live_project_key_here",
  brandId: "brand_abc123",
});

// Per-request key override
const results = await client.query(
  { intent: "search", query: "headphones" },
  { apiKey: "pk_live_different_key" }
);

Configuration Options

Every option can be set at client initialization or overridden per request.

TypeScript
const client = new CresvaClient({
  apiKey: process.env.CRESVA_API_KEY,
  brandId: "brand_abc123",

  // Timeout: max duration for a single HTTP request (ms)
  timeout: 30000,

  // Retries: the SDK retries on 429 and 5xx with exponential backoff
  maxRetries: 3,

  // Base URL: override for staging, proxies, or local development
  baseUrl: "https://staging.cresva.ai",

  // Custom headers: forwarded on every request
  defaultHeaders: {
    "X-Request-Source": "my-agent-v2",
    "X-Correlation-Id": "corr_abc123",
  },

  // HTTP agent: supply your own for connection pooling or proxying
  httpAgent: myCustomAgent,
});

// Override per request
const results = await client.query(
  { intent: "search", query: "headphones" },
  { timeout: 5000, maxRetries: 1 }
);

Core Methods

TypeScript
// Search products
const results = await client.query({
  intent: "search",
  query: "wireless headphones under $200",
  filters: { price: { max: 200, currency: "USD" } },
  limit: 10,
});

// Get a product
const product = await client.products.get("prod_h7k2m");

// List products
const products = await client.products.list({ category: "electronics" });

// Get recommendations
const recs = await client.recommend("prod_h7k2m", { type: "similar" });

// Compare products
const comparison = await client.compare({
  productIds: ["prod_h7k2m", "prod_x9y8z"],
});

// Negotiate
const negotiation = await client.negotiate({
  action: "initiate",
  productId: "prod_h7k2m",
  offeredPrice: 149.99,
  currency: "USD",
});

// Create transaction
const txn = await client.transactions.create({
  items: [{ productId: "prod_h7k2m", quantity: 1, price: 164.99 }],
  currency: "USD",
  shippingAddress: { line1: "123 Main St", city: "SF", state: "CA", postalCode: "94102", country: "US" },
});

// Get trust score
const trust = await client.trust.get();

TypeScript Types

Every request and response is fully typed. Import the types you need for strict type checking across your codebase.

TypeScript
import type {
  AgentQueryRequest,
  AgentQueryResponse,
  AgentProductCard,
  NegotiationRequest,
  NegotiationResponse,
  TransactionRequest,
  TransactionResponse,
  TrustScore,
} from "@cresva/sdk";
Type definitions (abridged)
interface AgentQueryRequest {
  intent: "search" | "recommend" | "compare" | "detail";
  query: string;
  filters?: {
    price?: { min?: number; max?: number; currency: string };
    category?: string;
    brand?: string;
    attributes?: Record<string, string | string[]>;
  };
  limit?: number;    // 1-100, default 20
  offset?: number;   // for pagination
  sort?: "relevance" | "price_asc" | "price_desc" | "rating" | "newest";
}

interface AgentQueryResponse {
  results: AgentProductCard[];
  total: number;
  hasMore: boolean;
  cursor?: string;
  meta: {
    queryId: string;
    latencyMs: number;
    appliedFilters: Record<string, unknown>;
  };
}

interface AgentProductCard {
  id: string;
  title: string;
  description: string;
  price: { amount: number; currency: string; formatted: string };
  images: { url: string; alt: string; width: number; height: number }[];
  rating?: { average: number; count: number };
  availability: "in_stock" | "low_stock" | "out_of_stock" | "preorder";
  attributes: Record<string, string>;
  url: string;
}

interface NegotiationRequest {
  action: "initiate" | "counter" | "accept" | "reject";
  productId: string;
  offeredPrice?: number;
  currency: string;
  negotiationId?: string;  // required for counter/accept/reject
  message?: string;
}

interface NegotiationResponse {
  negotiationId: string;
  status: "pending" | "countered" | "accepted" | "rejected" | "expired";
  offeredPrice: number;
  counterPrice?: number;
  expiresAt: string;       // ISO 8601
  message?: string;
}

interface TransactionRequest {
  items: { productId: string; quantity: number; price: number }[];
  currency: string;
  shippingAddress: {
    line1: string;
    line2?: string;
    city: string;
    state: string;
    postalCode: string;
    country: string;
  };
  negotiationId?: string;  // attach a completed negotiation
  metadata?: Record<string, string>;
}

interface TransactionResponse {
  transactionId: string;
  status: "created" | "processing" | "completed" | "failed" | "refunded";
  total: { amount: number; currency: string; formatted: string };
  items: { productId: string; quantity: number; unitPrice: number }[];
  createdAt: string;
}

interface TrustScore {
  brandId: string;
  overall: number;           // 0-100
  tier: "unverified" | "bronze" | "silver" | "gold" | "platinum";
  breakdown: {
    returnPolicy: number;
    fulfillmentSpeed: number;
    customerSatisfaction: number;
    dataTransparency: number;
    negotiationFairness: number;
  };
  updatedAt: string;
}

Error Handling

The SDK throws typed errors for every failure mode. All errors extend CresvaError, which includes the HTTP status code, a machine-readable error code, and a human-readable message.

TypeScript
import { CresvaError, RateLimitError, NotFoundError } from "@cresva/sdk";

try {
  const results = await client.query({ intent: "search", query: "headphones" });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log("Rate limited, retry after:", error.retryAfter);
  } else if (error instanceof NotFoundError) {
    console.log("Product not found");
  } else if (error instanceof CresvaError) {
    console.log("API error:", error.code, error.message);
  }
}
Full error class hierarchy
import {
  CresvaError,           // Base class for all API errors
  AuthenticationError,   // 401 - invalid or missing API key
  PermissionError,       // 403 - key lacks required scope
  NotFoundError,         // 404 - resource does not exist
  ValidationError,       // 422 - request body failed validation
  RateLimitError,        // 429 - too many requests
  InternalError,         // 500 - server error (auto-retried)
  ConnectionError,       // Network-level failure (auto-retried)
  TimeoutError,          // Request exceeded timeout (auto-retried)
} from "@cresva/sdk";

// Every CresvaError exposes:
// error.status    - HTTP status code (number)
// error.code      - machine-readable code (string), e.g. "rate_limit_exceeded"
// error.message   - human-readable message
// error.requestId - unique ID for support tickets
// error.headers   - raw response headers

// Exhaustive error handling pattern
async function safeQuery(query: string) {
  try {
    return await client.query({ intent: "search", query });
  } catch (error) {
    if (error instanceof RateLimitError) {
      // Back off and retry after the server-specified delay
      await sleep(error.retryAfter * 1000);
      return client.query({ intent: "search", query });
    }
    if (error instanceof ValidationError) {
      // Log the field-level errors for debugging
      console.error("Validation failed:", error.message);
      for (const issue of error.issues ?? []) {
        console.error(`  ${issue.path}: ${issue.message}`);
      }
      throw error;
    }
    if (error instanceof AuthenticationError) {
      console.error("Check your API key:", error.message);
      throw error;
    }
    if (error instanceof CresvaError) {
      console.error(`[Request ${error.requestId}] ${error.code}: ${error.message}`);
      throw error;
    }
    // Non-API errors (network, etc.)
    throw error;
  }
}

Middleware and Interceptors

Add middleware to observe, modify, or gate every request and response. Middleware runs in the order it is registered.

TypeScript
import { CresvaClient } from "@cresva/sdk";

const client = new CresvaClient({
  apiKey: process.env.CRESVA_API_KEY,
  brandId: "brand_abc123",
});

// Request middleware: runs before each HTTP call
client.use("request", async (req, next) => {
  const start = Date.now();
  console.log(`--> ${req.method} ${req.path}`);

  // Modify the request (e.g. inject a tracing header)
  req.headers["X-Trace-Id"] = crypto.randomUUID();

  const res = await next(req);

  console.log(`<-- ${res.status} (${Date.now() - start}ms)`);
  return res;
});

// Response middleware: transform or log responses
client.use("response", async (res, next) => {
  // Log rate limit headers for observability
  const remaining = res.headers["x-ratelimit-remaining"];
  if (remaining && Number(remaining) < 10) {
    console.warn(`Rate limit warning: ${remaining} requests remaining`);
  }
  return next(res);
});

// Error middleware: centralized error handling
client.use("error", async (error, next) => {
  // Report to your error tracking service
  await reportToSentry(error);
  return next(error);
});

Pagination

The SDK supports both cursor-based and offset-based pagination. Use the async iterator for automatic page fetching.

TypeScript
// Automatic pagination with async iterator
for await (const product of client.products.list({ category: "electronics" })) {
  console.log(product.title);
}

// Manual cursor-based pagination
let cursor: string | undefined;
do {
  const page = await client.query({
    intent: "search",
    query: "headphones",
    limit: 20,
    ...(cursor ? { cursor } : {}),
  });
  for (const result of page.results) {
    process.stdout.write(result.title + "\n");
  }
  cursor = page.hasMore ? page.cursor : undefined;
} while (cursor);

// Collect all results into an array (caution: may be large)
const allProducts = await client.products.list({ category: "electronics" }).toArray();
console.log(`Fetched ${allProducts.length} products`);

Streaming

For real-time use cases such as agent UIs, the SDK can stream query results as they arrive rather than waiting for the full response.

TypeScript
// Stream search results as they arrive
const stream = client.query.stream({
  intent: "search",
  query: "wireless headphones",
  limit: 20,
});

stream.on("result", (product) => {
  // Each product arrives individually as the server processes it
  renderProductCard(product);
});

stream.on("meta", (meta) => {
  console.log(`Query ${meta.queryId} completed in ${meta.latencyMs}ms`);
});

stream.on("error", (error) => {
  console.error("Stream error:", error.message);
});

// Or use the async iterator
for await (const event of stream) {
  if (event.type === "result") {
    renderProductCard(event.data);
  }
}

Batch Operations

Fetch multiple resources in a single round-trip. The batch API reduces latency and counts as a single rate-limit hit.

TypeScript
// Batch product lookups
const products = await client.products.getBatch([
  "prod_h7k2m",
  "prod_x9y8z",
  "prod_a1b2c",
]);
// Returns: AgentProductCard[] in the same order as the input IDs.
// Missing products are returned as null.

// Batch queries: run multiple intents in parallel server-side
const [searchResults, recommendations] = await client.batch([
  { method: "query", params: { intent: "search", query: "headphones" } },
  { method: "recommend", params: { productId: "prod_h7k2m", type: "similar" } },
]);

// Batch with error isolation: one failure does not abort others
const results = await client.batch(
  [
    { method: "products.get", params: { id: "prod_h7k2m" } },
    { method: "products.get", params: { id: "prod_invalid" } },
    { method: "trust.get", params: {} },
  ],
  { failureMode: "isolate" }  // default is "abort"
);
// results[0] -> { status: "success", data: AgentProductCard }
// results[1] -> { status: "error", error: NotFoundError }
// results[2] -> { status: "success", data: TrustScore }

Testing

The SDK ships with testing utilities so you can mock API responses without hitting the network.

Mocking with the built-in test helper
import { CresvaClient, createMockClient } from "@cresva/sdk/testing";

// createMockClient returns a fully-typed client where every method
// is a jest.fn() / vi.fn() that you can stub.
const mockClient = createMockClient();

mockClient.query.mockResolvedValue({
  results: [
    {
      id: "prod_test1",
      title: "Mock Headphones",
      price: { amount: 99.99, currency: "USD", formatted: "$99.99" },
      availability: "in_stock",
      description: "Great headphones for testing",
      images: [],
      attributes: {},
      url: "https://example.com/mock",
    },
  ],
  total: 1,
  hasMore: false,
  meta: { queryId: "q_test", latencyMs: 12, appliedFilters: {} },
});

// Inject the mock into your application code
const results = await mockClient.query({
  intent: "search",
  query: "headphones",
});
expect(results.results).toHaveLength(1);
expect(mockClient.query).toHaveBeenCalledWith(
  expect.objectContaining({ intent: "search" })
);
Mocking with MSW (Mock Service Worker)
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { CresvaClient } from "@cresva/sdk";

const server = setupServer(
  http.post("https://api.cresva.ai/api/storefront/*/query", () => {
    return HttpResponse.json({
      results: [{ id: "prod_mock", title: "MSW Mock Product" }],
      total: 1,
      hasMore: false,
      meta: { queryId: "q_mock", latencyMs: 5, appliedFilters: {} },
    });
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test("searches products", async () => {
  const client = new CresvaClient({
    apiKey: "pk_test_fake",
    brandId: "brand_test",
  });

  const results = await client.query({
    intent: "search",
    query: "headphones",
  });
  expect(results.total).toBe(1);
});

Python SDK

Python SDK in preview
The Python SDK is in preview. The TypeScript SDK is live; Python ships in v2.

Installation

The SDK requires Python 3.9 or later. Install it with your preferred package manager.

pip install cresva

The package includes inline type stubs (PEP 561), so type checkers like mypy and pyright work out of the box.

Initialization

Python
from cresva import CresvaClient

client = CresvaClient(
    api_key="pk_live_your_key_here",  # or use CRESVA_API_KEY env var
    brand_id="brand_abc123",
    # Optional:
    timeout=30,         # Request timeout in seconds
    max_retries=3,      # Auto-retry on transient errors
)

# Async client
from cresva import AsyncCresvaClient

async_client = AsyncCresvaClient(
    api_key="pk_live_your_key_here",
    brand_id="brand_abc123",
)

Authentication

Like the JavaScript SDK, the Python SDK supports publishable keys for read-only operations and secret keys for write operations. The client reads CRESVA_API_KEY from the environment automatically.

Python
import os

# Automatic: reads CRESVA_API_KEY from environment
client = CresvaClient(brand_id="brand_abc123")

# Explicit key
client = CresvaClient(
    api_key="sk_live_project_key_here",
    brand_id="brand_abc123",
)

# Per-request key override
results = client.query(
    intent="search",
    query="headphones",
    request_options={"api_key": "pk_live_different_key"},
)

Configuration Options

Python
import httpx
from cresva import CresvaClient

client = CresvaClient(
    api_key="sk_live_your_key_here",
    brand_id="brand_abc123",

    # Timeout: max duration for a single HTTP request (seconds)
    timeout=30,

    # Retries: the SDK retries on 429 and 5xx with exponential backoff
    max_retries=3,

    # Base URL: override for staging, proxies, or local development
    base_url="https://staging.cresva.ai",

    # Custom headers: forwarded on every request
    default_headers={
        "X-Request-Source": "my-agent-v2",
        "X-Correlation-Id": "corr_abc123",
    },

    # HTTP client: supply your own httpx.Client for proxying or custom TLS
    http_client=httpx.Client(proxy="http://proxy.internal:8080"),
)

# Override per request
results = client.query(
    intent="search",
    query="headphones",
    request_options={"timeout": 5, "max_retries": 1},
)

Core Methods

Python
# Search products
results = client.query(
    intent="search",
    query="wireless headphones under $200",
    filters={"price": {"max": 200, "currency": "USD"}},
    limit=10,
)

# Get a product
product = client.products.get("prod_h7k2m")

# List products with automatic pagination
for product in client.products.list(category="electronics"):
    print(product.title, product.price)

# Negotiate
negotiation = client.negotiate(
    action="initiate",
    product_id="prod_h7k2m",
    offered_price=149.99,
    currency="USD",
)

# Create transaction
txn = client.transactions.create(
    items=[{"product_id": "prod_h7k2m", "quantity": 1, "price": 164.99}],
    currency="USD",
    shipping_address={
        "line1": "123 Main St",
        "city": "San Francisco",
        "state": "CA",
        "postal_code": "94102",
        "country": "US",
    },
)

# Get trust score
trust = client.trust.get()
print(f"Trust: {trust.overall}/100 ({trust.tier})")

Type Hints and Models

All response objects are Pydantic models with full type annotations. This gives you autocompletion, runtime validation, and serialization for free.

Python
from cresva.types import (
    AgentQueryRequest,
    AgentQueryResponse,
    AgentProductCard,
    NegotiationRequest,
    NegotiationResponse,
    TransactionRequest,
    TransactionResponse,
    TrustScore,
    Price,
    ShippingAddress,
)

# Response objects are typed Pydantic models
results: AgentQueryResponse = client.query(
    intent="search",
    query="headphones",
)

# Access fields with type safety
for product in results.results:
    # product is AgentProductCard
    title: str = product.title
    price: Price = product.price
    print(f"{title}: {price.formatted}")

# Serialize to dict or JSON
product_dict = product.model_dump()
product_json = product.model_dump_json()

# Construct request objects explicitly (useful for validation)
request = AgentQueryRequest(
    intent="search",
    query="headphones",
    filters={"price": {"max": 200, "currency": "USD"}},
    limit=10,
)
results = client.query(**request.model_dump())

Error Handling

Python
from cresva.errors import CresvaError, RateLimitError, NotFoundError

try:
    results = client.query(intent="search", query="headphones")
except RateLimitError as e:
    print(f"Rate limited, retry after: {e.retry_after}s")
except NotFoundError:
    print("Product not found")
except CresvaError as e:
    print(f"API error: {e.code} - {e.message}")
Full error hierarchy
from cresva.errors import (
    CresvaError,           # Base class for all API errors
    AuthenticationError,   # 401 - invalid or missing API key
    PermissionError,       # 403 - key lacks required scope
    NotFoundError,         # 404 - resource does not exist
    ValidationError,       # 422 - request body failed validation
    RateLimitError,        # 429 - too many requests
    InternalError,         # 500 - server error (auto-retried)
    ConnectionError,       # Network-level failure (auto-retried)
    TimeoutError,          # Request exceeded timeout (auto-retried)
)

# Every CresvaError exposes:
# error.status      - HTTP status code (int)
# error.code        - machine-readable code (str), e.g. "rate_limit_exceeded"
# error.message     - human-readable message
# error.request_id  - unique ID for support tickets
# error.headers     - raw response headers (dict)

import time

def safe_query(query: str) -> AgentQueryResponse:
    try:
        return client.query(intent="search", query=query)
    except RateLimitError as e:
        time.sleep(e.retry_after)
        return client.query(intent="search", query=query)
    except ValidationError as e:
        print(f"Validation failed: {e.message}")
        for issue in e.issues or []:
            print(f"  {issue['path']}: {issue['message']}")
        raise
    except AuthenticationError as e:
        print(f"Check your API key: {e.message}")
        raise
    except CresvaError as e:
        print(f"[Request {e.request_id}] {e.code}: {e.message}")
        raise

Middleware and Hooks

The Python SDK uses a hook system for request/response interception. Hooks receive and return the request or response object.

Python
import time
import uuid
from cresva import CresvaClient

client = CresvaClient(
    api_key="sk_live_your_key_here",
    brand_id="brand_abc123",
)

# Request hook: runs before each HTTP call
@client.on("request")
def log_request(req):
    req.headers["X-Trace-Id"] = str(uuid.uuid4())
    print(f"--> {req.method} {req.path}")
    req._start_time = time.time()
    return req

# Response hook: runs after each HTTP response
@client.on("response")
def log_response(res):
    elapsed = time.time() - res.request._start_time
    print(f"<-- {res.status_code} ({elapsed:.2f}s)")

    remaining = res.headers.get("x-ratelimit-remaining")
    if remaining and int(remaining) < 10:
        print(f"Rate limit warning: {remaining} requests remaining")
    return res

# Error hook: centralized error handling
@client.on("error")
def report_error(error):
    sentry_sdk.capture_exception(error)
    return error

Pagination

Python
# Automatic pagination with iterator (sync)
for product in client.products.list(category="electronics"):
    print(product.title)

# Automatic pagination with async iterator
async for product in async_client.products.list(category="electronics"):
    print(product.title)

# Manual cursor-based pagination
cursor = None
while True:
    page = client.query(
        intent="search",
        query="headphones",
        limit=20,
        **({"cursor": cursor} if cursor else {}),
    )
    for result in page.results:
        print(result.title)
    if not page.has_more:
        break
    cursor = page.cursor

# Collect all results into a list
all_products = list(client.products.list(category="electronics"))
print(f"Fetched {len(all_products)} products")

Streaming

Python
# Stream search results as they arrive (sync)
with client.query.stream(intent="search", query="wireless headphones", limit=20) as stream:
    for event in stream:
        if event.type == "result":
            print(f"Got product: {event.data.title}")
        elif event.type == "meta":
            print(f"Query {event.data.query_id} completed in {event.data.latency_ms}ms")

# Async streaming
async with async_client.query.stream(intent="search", query="headphones") as stream:
    async for event in stream:
        if event.type == "result":
            await render_product_card(event.data)

Batch Operations

Python
# Batch product lookups
products = client.products.get_batch([
    "prod_h7k2m",
    "prod_x9y8z",
    "prod_a1b2c",
])
# Returns: list[AgentProductCard | None] in input order

# Batch queries: run multiple intents in parallel server-side
search_results, recommendations = client.batch([
    {"method": "query", "params": {"intent": "search", "query": "headphones"}},
    {"method": "recommend", "params": {"product_id": "prod_h7k2m", "type": "similar"}},
])

# Batch with error isolation
results = client.batch(
    [
        {"method": "products.get", "params": {"id": "prod_h7k2m"}},
        {"method": "products.get", "params": {"id": "prod_invalid"}},
        {"method": "trust.get", "params": {}},
    ],
    failure_mode="isolate",  # default is "abort"
)
# results[0] -> BatchSuccess(data=AgentProductCard(...))
# results[1] -> BatchError(error=NotFoundError(...))
# results[2] -> BatchSuccess(data=TrustScore(...))

Testing

The SDK includes test utilities for mocking API responses without network calls.

Mocking with the built-in test helper
from cresva.testing import create_mock_client, mock_response
from cresva.types import AgentQueryResponse, AgentProductCard, Price

# create_mock_client returns a client where every method is a MagicMock
mock_client = create_mock_client()

mock_client.query.return_value = AgentQueryResponse(
    results=[
        AgentProductCard(
            id="prod_test1",
            title="Mock Headphones",
            description="Great headphones for testing",
            price=Price(amount=99.99, currency="USD", formatted="$99.99"),
            images=[],
            availability="in_stock",
            attributes={},
            url="https://example.com/mock",
        ),
    ],
    total=1,
    has_more=False,
    meta={"query_id": "q_test", "latency_ms": 12, "applied_filters": {}},
)

# Use the mock in your tests
results = mock_client.query(intent="search", query="headphones")
assert len(results.results) == 1
mock_client.query.assert_called_once()
Mocking with pytest and respx
import respx
import httpx
import pytest
from cresva import CresvaClient

@respx.mock
def test_search_products():
    respx.post("https://api.cresva.ai/api/storefront/brand_test/query").mock(
        return_value=httpx.Response(
            200,
            json={
                "results": [{"id": "prod_mock", "title": "Mock Product"}],
                "total": 1,
                "has_more": False,
                "meta": {"query_id": "q_mock", "latency_ms": 5, "applied_filters": {}},
            },
        )
    )

    client = CresvaClient(
        api_key="pk_test_fake",
        brand_id="brand_test",
    )

    results = client.query(intent="search", query="headphones")
    assert results.total == 1

cURL Examples

Every endpoint can be accessed directly with cURL. Replace pk_live_your_key_here with your API key and brand_abc123 with your brand ID.

Search Products

bash
curl "https://api.cresva.ai/api/storefront/brand_abc123/search?q=wireless+headphones&max_price=200" \
  -H "Authorization: Bearer pk_live_your_key_here"

Query Products

bash
curl -X POST "https://api.cresva.ai/api/storefront/brand_abc123/query" \
  -H "Authorization: Bearer pk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "search",
    "query": "noise-cancelling headphones",
    "filters": {"price": {"max": 200, "currency": "USD"}},
    "limit": 5
  }'

Initiate Negotiation

bash
curl -X POST "https://api.cresva.ai/api/storefront/brand_abc123/negotiate" \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "initiate",
    "product_id": "prod_h7k2m",
    "offered_price": 149.99,
    "currency": "USD"
  }'

Check Health

bash
curl "https://api.cresva.ai/api/storefront/brand_abc123/health"

Migration Guide: v1 to v2

SDK v2 introduces a cleaner API surface, improved type safety, and new features like streaming and batch operations. This guide covers every breaking change.

Client Initialization

The constructor options were renamed for consistency across SDKs.

TypeScript
// v1 - DEPRECATED
import Cresva from "@cresva/sdk";

const client = new Cresva({
  key: "pk_live_...",
  brand: "brand_abc123",
  retries: 3,
  timeoutMs: 30000,
});

Query Method

The search() method was replaced by the unified query() method that supports multiple intents.

TypeScript
// v1 - DEPRECATED
const results = await client.search("headphones", {
  maxPrice: 200,
  currency: "USD",
});

Error Classes

Error class names were made more specific. The generic APIError was replaced with CresvaError and its subclasses.

TypeScript
// v1 - DEPRECATED
import { APIError } from "@cresva/sdk";

try {
  await client.search("headphones");
} catch (e) {
  if (e instanceof APIError && e.status === 429) {
    // handle rate limit
  }
}

Response Shape

Response objects are now typed classes instead of plain objects. Property access remains the same, but serialization methods have changed.

TypeScript
// v1 - responses were plain objects
const results = await client.search("headphones");
const data = JSON.parse(JSON.stringify(results));

Python-Specific Changes

v1 to v2 changes
# v1 - DEPRECATED
from cresva import Cresva
client = Cresva(key="pk_live_...", brand="brand_abc123")
results = client.search("headphones", max_price=200)

# v2
from cresva import CresvaClient
client = CresvaClient(api_key="pk_live_...", brand_id="brand_abc123")
results = client.query(
    intent="search",
    query="headphones",
    filters={"price": {"max": 200, "currency": "USD"}},
)

# v1 - manual pagination
page = client.products.list(page=1, per_page=20)
next_page = client.products.list(page=2, per_page=20)

# v2 - automatic pagination via iterator
for product in client.products.list(category="electronics"):
    print(product.title)

# v1 - dict responses
results["products"][0]["title"]

# v2 - typed model responses
results.results[0].title

Full Changelog

  • BreakingRenamed constructor: Cresva to CresvaClient
  • BreakingRenamed option: key to apiKey (JS) / api_key (Python)
  • BreakingRenamed option: brand to brandId (JS) / brand_id (Python)
  • BreakingReplaced search() with query()
  • BreakingReplaced APIError with CresvaError hierarchy
  • BreakingResponse objects are now typed classes, not plain dicts/objects
  • NewStreaming support via client.query.stream()
  • NewBatch operations via client.batch()
  • NewAutomatic pagination with async iterators
  • NewMiddleware / hooks system
  • NewBuilt-in test utilities (@cresva/sdk/testing / cresva.testing)
  • NewPer-request option overrides