|

Documentation

Getting Started

Welcome to the Last Price platform documentation. This guide explains pricing, integrations, and workspace workflows. Use the API reference for the current endpoint contracts.

Base URL & Authentication

Use this base URL for API requests. Secured operations accept the credentials listed in the API reference, commonly a JWT bearer token or an API key passed via the x-api-key header.

# Base URL
https://api.last-price.ai

# Example authenticated request
curl https://api.last-price.ai/api/me \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Ways to Access the API

Building an agent? Read /llms.txt first. It is the current word on getting a key with no human, keyless calls, pay per call, limits and error fields, and this guide does not repeat it. Machine-readable versions: the OpenAPI contract, the agent manifest, the MCP manifest and the changelog feed.

Choose one way to start. These options and limits come from this deployment's current discovery document.

Open the API reference for request bodies and response schemas

Loading current access options...

Platform Overview

Last Price is a pricing optimization and A/B testing platform that helps businesses find the price that maximizes revenue.

ComponentWhat It Does
EloA/B testing engine - deterministic variant assignment via SHA-256 hashing
JaleAI pricing optimizer - elasticity analysis, revenue simulation, recommendations
PCNPricing Compute Network - function registry, inference routing, metering
Price RouterMulti-vendor pricing facade - one API in front of all built-in and custom pricing models
RosettaSchema translation - map any price data format to canonical schemas with AI-assisted adapters
Commerce Control PlaneEnd-to-end pipeline - connectors, observations, policies, approvals, execution
OjaAdmin dashboard - manage experiments, functions, usage, marketplace
SnippetEmbeddable JavaScript widget - zero-dependency, drop into any website
APIREST API - full programmatic control over every feature
MarketplacePublish and consume community pricing functions with revenue sharing
ScraperCompetitive pricing data collection from any website

Quick Start

  1. Set Up Your Workspace

    For this experiment workflow, sign up and create an API key in Settings, API keys. If you started with a sandbox, use its claim link to move into a full workspace first. Discover your tenant_id with GET /api/me using that key, then use it in the experiment URL below. Keep the key on your server and send it in x-api-key. If using OAuth, send the access token in Authorization: Bearer instead.

    Create a workspace
  2. Create Your First Experiment
    curl -X POST https://api.last-price.ai/api/tenants/YOUR_TENANT_ID/experiments \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Homepage Pricing Test",
        "key": "homepage_price_test",
        "variants": [
          { "name": "control",    "price": 29.99, "weight": 50 },
          { "name": "experiment", "price": 39.99, "weight": 50 }
        ]
      }'
  3. Choose an Integration Method
    MethodBest ForEffort
    Shopify, WordPress, any HTML site5 minutes
    Next.js, React apps15 minutes
    Any backend or frontend30 minutes

Platform Integrations

Step-by-step integration guides for Shopify, SaaS apps, e-commerce platforms, marketplaces, and digital products.

Shopify Store Integration

Integrate Last Price to A/B test product prices, subscription tiers, and promotional offers in your Shopify store.

Option A: Embed Snippet (No Coding)

Go to Shopify Admin → Online Store → Themes → Edit Code → theme.liquid. Add before the closing </body> tag:

<!-- Last Price A/B Testing -->
<script src="https://last-price.ai/lastprice.js"></script>
<script>
  LastPrice.configure({
    apiBase: 'https://api.last-price.ai',
    embedToken: 'lpe_YOUR_EMBED_TOKEN',
    tenantId: 'YOUR_TENANT_ID',
    experimentId: 'YOUR_EXPERIMENT_KEY',
    debug: false
  });
</script>

Add a pricing container to your product template:

<div id="lastprice-pricing"></div>

<script>
  LastPrice.showPricing('#lastprice-pricing', {
    customRenderer: function(pricing) {
      // Escaped because this string is assigned to innerHTML and the value
      // comes from the variant's metadata, which can carry markup.
      var price = String(pricing.price)
        .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
      return '<div class="product-price">' +
        '<span class="money">' + price + '</span></div>';
    }
  });

  // Record the conversion from Shopify's own Add to Cart form.
  // onConvert is not used here: the SDK attaches it to a button with id
  // lp-convert-btn, which the default renderer emits and a custom one like
  // the above does not. Give your rendered button that id if you would
  // rather use onConvert.
  document.addEventListener('submit', function (event) {
    var action = event.target && event.target.action;
    if (action && action.indexOf('/cart/add') !== -1) {
      // Record what this visitor was shown. A variant's metadata can
      // override the price, so leaving revenue out would store the
      // variant's own figure and disagree with the page.
      var shown = LastPrice.getVariant();
      LastPrice.convert(shown && typeof shown.price === 'number'
        ? { revenue: shown.price }
        : {});
    }
  });
</script>

Option B: Headless / Custom Storefront

Note: @lastprice/client is a hypothetical SDK package name shown for illustration. Use the REST API directly.

This example runs on your server, so it sends your secret API key. Do not move these calls into the browser as they are: a page has no credential here, and the cross-origin preflight does not allow the header this uses. For a browser component, configure the snippet with an embed token instead and register the page origin on it.

import { cookies } from 'next/headers';

const API_BASE = 'https://api.last-price.ai';
const TENANT_ID = 'YOUR_TENANT_ID';

export default async function ProductPage({ product }) {
  // A Server Component has no localStorage, so the participant id comes from
  // a cookie. It has to already exist: minting one here would mint a new one
  // on every render, and assignment is stable per participant, so the visitor
  // would move between variants as they browsed. Set it once at the edge (see
  // the middleware below) and read it everywhere else.
  const userId = (await cookies()).get('lp_uid')?.value;
  if (!userId) return null; // middleware sets it on the first request

  // Get the A/B tested price via the pricing endpoint
  // Server side, so the secret key is safe here. Never ship this to a browser.
  const resp = await fetch(
    `${API_BASE}/api/experiments/product_price_test/pricing?userId=${userId}&tenantId=${TENANT_ID}`,
    { headers: { 'x-api-key': process.env.LASTPRICE_API_KEY } }
  );
  const { pricing } = await resp.json();

  return (
    <div>
      <h1>{product.title}</h1>
      <p className="price">${pricing.price}</p>
      <AddToCart userId={userId} revenue={pricing.price} />
    </div>
  );
}

The participant id is minted once, at the edge, so every later render reads the same one and the visitor stays in one variant:

// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
  if (request.cookies.get('lp_uid')) return NextResponse.next();

  const response = NextResponse.next();
  response.cookies.set('lp_uid', crypto.randomUUID(), {
    maxAge: 60 * 60 * 24 * 365,
    sameSite: 'lax',
    path: '/'
  });
  return response;
}

The button is a client component, because a click handler cannot be passed out of a Server Component. It posts to a route on your own domain, which holds the key:

'use client';

// app/product/add-to-cart.jsx
export function AddToCart({ userId, revenue }) {
  return (
    <button
      onClick={async () => {
        // Your own route, which holds the secret key. Posting straight to the
        // API from a page would carry no credential the API accepts.
        await fetch('/api/record-conversion', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ userId, revenue })
        });
      }}
    >
      Add to Cart
    </button>
  );
}

Shopify Use Cases

Use CaseExperiment Setup
Product price testingVariant A: $49.99 vs Variant B: $54.99
Subscription tier pricingVariant A: $9/mo vs Variant B: $12/mo
Bundle pricingVariant A: $79 bundle vs Variant B: $89 bundle
Discount depth testingVariant A: 10% off vs Variant B: 20% off
Free shipping thresholdVariant A: free at $50 vs Variant B: free at $75
Psychological pricingVariant A: $49.99 vs Variant B: $50.00

SaaS / Subscription Integration

Test pricing tiers, plans, and subscription models in React/Next.js apps.

Note: @lastprice/react is a hypothetical SDK package name shown for illustration. Use the REST API directly.

"use client";
import { useState, useEffect } from 'react';

export default function PricingPage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const userId = localStorage.getItem('lp_uid') || crypto.randomUUID();
    localStorage.setItem('lp_uid', userId);
    fetch(`/api/experiments/saas_pricing_test/pricing?userId=${userId}&tenantId=YOUR_TENANT_ID`)
      .then(r => r.json()).then(setData).finally(() => setLoading(false));
  }, []);

  if (loading || !data) return <PricingSkeleton />;

  return (
    <PricingCard
      name={data.variant === 'control' ? 'Starter' : 'Growth'}
      price={data.pricing.price}
      features={data.pricing.features}
      onSubscribe={async () => {
        await fetch('/api/experiments/saas_pricing_test/convert', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            userId: localStorage.getItem('lp_uid'),
            tenantId: 'YOUR_TENANT_ID',
            revenue: data.pricing.price
          })
        });
        window.location.href = `/api/checkout?plan=${data.variant}&price=${data.pricing.price}`;
      }}
    />
  );
}

Use Jale for AI-powered price recommendations:

curl -X POST https://api.last-price.ai/api/compute/price \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: YOUR_TENANT_ID" \
  -d '{
    "function_name": "lastprice/jale-optimizer",
    "input": {
      "current_price": 29,
      "product_type": "saas_subscription",
      "market_data": {
        "competitor_prices": [19, 25, 39, 49],
        "current_conversion_rate": 0.03
      }
    }
  }'

E-commerce Platform Integration

Server-side price assignment for e-commerce platforms (recommended for SEO and consistency).

// pages/api/product-price.ts (Next.js API route)
// Uses the REST API directly
export default async function handler(req, res) {
  const { userId, productId } = req.query;
  const tenantId = process.env.TENANT_ID;

  const resp = await fetch(
    `${process.env.LASTPRICE_API_URL}/api/experiments/product_${productId}_price/pricing?userId=${userId}&tenantId=${tenantId}`
  );
  const data = await resp.json();

  res.json({
    productId,
    price: data.pricing.price,
    variant: data.variant,
    experimentId: data.experimentId
  });
}

Track conversions after a successful payment:

// After successful payment - track conversion via REST API
async function onPaymentSuccess(order) {
  await fetch(`${process.env.LASTPRICE_API_URL}/api/experiments/${order.experimentKey}/convert`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      userId: order.userId,
      tenantId: process.env.TENANT_ID,
      revenue: order.totalAmount
    })
  });
}

Marketplace Integration

For platforms connecting buyers and sellers - test commission rates, platform fees, and pricing models.

// Test different commission rates using the REST API
const resp = await fetch(
  `${API_BASE}/api/experiments/commission_rate_test/pricing?userId=${sellerId}&tenantId=${TENANT_ID}`
);
const { variant, pricing } = await resp.json();
const commissionRate = variant === 'control' ? 0.10 : 0.15;
const platformFee = transactionAmount * commissionRate;

// Track when a transaction completes
await fetch(`${API_BASE}/api/experiments/commission_rate_test/convert`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ userId: sellerId, tenantId: TENANT_ID, revenue: platformFee })
});

Digital Products Integration

For courses, ebooks, templates, and downloadable content.

// Test course pricing ($199 control vs $299 premium positioning)
const resp = await fetch(
  `${API_BASE}/api/experiments/course_pricing_test/pricing?userId=${userId}&tenantId=${TENANT_ID}`
);
const { pricing } = await resp.json();
const coursePrice = pricing.price;

// Track enrollment as conversion
await fetch(`${API_BASE}/api/experiments/course_pricing_test/convert`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    userId, tenantId: TENANT_ID, revenue: coursePrice
  })
});

Embed Snippet (No-Code)

The fastest way to add Last Price to any website. No build tools needed, just a drop-in JavaScript widget.

The snippet authenticates with an embed token, created on the Embed Snippet page. It is publishable, so it is safe in your page source: it reaches the pricing lookup and the conversion beacon only, and it is rate limited per client address (visitors sharing an address share a budget). Calls from a browser work only from the sites you register on it, which is what stops someone copying the token onto their own page. That is a browser check, not proof of where a request came from: a script outside a browser can claim any site. Anything that has to be trustworthy, revenue reporting included, belongs on your server with your secret API key, which stays there, where nobody viewing a page can read it. A page on another origin cannot send one at all, because the cross-origin preflight does not allow the header that carries it, though that follows from crossing an origin rather than from anything protecting a key you have published.

Basic Setup

<div id="pricing-container"></div>

<script src="https://last-price.ai/lastprice.js"></script>
<script>
  LastPrice.configure({
    apiBase: 'https://api.last-price.ai',
    embedToken: 'lpe_YOUR_EMBED_TOKEN',
    tenantId: 'YOUR_TENANT_ID',
    experimentId: 'YOUR_EXPERIMENT_KEY'
  });

  LastPrice.showPricing('#pricing-container');
</script>

Auto-Initialize via Data Attributes

<div id="pricing-container"></div>

<script
  src="https://last-price.ai/lastprice.js"
  data-api-base="https://api.last-price.ai"
  data-embed-token="lpe_YOUR_EMBED_TOKEN"
  data-tenant-id="YOUR_TENANT_ID"
  data-experiment-id="YOUR_EXPERIMENT_KEY"
  data-container="#pricing-container"
  data-auto-load
></script>

Custom Rendering

LastPrice.showPricing('#pricing-container', {
  customRenderer: function(pricing) {
    // The plan name and the features come from the variant's metadata and
    // end up in innerHTML, so escape them the way the default card does.
    var esc = function(value) {
      return String(value)
        .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
    };

    return '<div class="my-pricing-card">' +
      '<h2>' + esc(pricing.plan) + '</h2>' +
      '<p class="price">$' + esc(pricing.price) + '/mo</p>' +
      '<button id="lp-convert-btn">Subscribe Now</button>' +
    '</div>';
  },
  onConvert: async function(pricing) {
    await LastPrice.convert({ revenue: pricing.price });
    window.location.href = '/checkout?price=' + pricing.price;
  }
});

Snippet API Reference

MethodDescription
LastPrice.configure(options)Set API base URL, embed token, tenant ID, experiment key
LastPrice.showPricing(selector, options?)Fetch variant and render pricing card
LastPrice.convert(options?)Record a conversion event with optional revenue
LastPrice.getUserId()Get the current user's persistent ID (cookie-based)
LastPrice.getVariant()Get the current variant assignment

API Client (Code-Level)

Full programmatic control over Last Price features using TypeScript, React hooks, REST, or Python.

The server samples below carry your secret API key in the x-api-key header. Without it the API answers 401. They show the header once and then leave it out for brevity; add it to each request. The React sample is the exception and is browser code: it calls a route on its own origin, which is the proxy pattern, so the key stays on the server behind it. Calling the API from a page directly is a different thing again: only the pricing lookup and the conversion beacon answer a cross-origin call at all, and only with a publishable embed token, which is what the snippet above does. A secret key cannot be used from another origin and must never be put in page source.

TypeScript / JavaScript Client

Note: @lastprice/client is a hypothetical SDK package name shown for illustration. In this repo, use the REST API directly.

const API_BASE = 'https://api.last-price.ai';
const TENANT_ID = 'YOUR_TENANT_ID';

// Server side only: this key must never reach a browser.
const HEADERS = { 'x-api-key': process.env.LASTPRICE_API_KEY };

// 1. Get pricing (assigns a variant automatically)
const pricingResp = await fetch(
  `${API_BASE}/api/experiments/pricing_experiment_1/pricing?userId=user_123&tenantId=${TENANT_ID}`,
  { headers: HEADERS }
);
const { variant, pricing } = await pricingResp.json();
console.log(variant);       // "control" or "experiment"
console.log(pricing.price); // 29.99 or 39.99

// 2. Track a conversion
await fetch(`${API_BASE}/api/experiments/pricing_experiment_1/convert`, {
  method: 'POST',
  headers: { ...HEADERS, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    userId: 'user_123',
    tenantId: TENANT_ID,
    revenue: 29.99
  })
});

React Hooks

Note: @lastprice/react is a hypothetical SDK package name shown for illustration. Use the REST API with fetch in a custom hook, as below.

import { useState, useEffect } from 'react';

// Browser code. The path is relative, so this calls a route on your own
// origin that forwards to the API with your secret key. To call the API
// directly from the page instead, use the snippet and its embed token: those
// are the only two operations a browser may reach.
function usePricing(experimentKey, tenantId) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const userId = localStorage.getItem('lp_uid') || crypto.randomUUID();
    localStorage.setItem('lp_uid', userId);
    fetch(`/api/experiments/${experimentKey}/pricing?userId=${userId}&tenantId=${tenantId}`)
      .then(r => r.json()).then(setData).finally(() => setLoading(false));
  }, [experimentKey, tenantId]);

  return { ...data, loading };
}

function PricingPage() {
  const { variant, pricing, loading } = usePricing('my_experiment', 'YOUR_TENANT_ID');

  if (loading) return <div>Loading prices...</div>;

  return (
    <div>
      <h2>Plan: {pricing?.plan}</h2>
      <p>Price: ${pricing?.price}/month</p>
    </div>
  );
}

REST API (Any Language)

# Assign a variant. The tenant comes from your API key.
curl "https://api.last-price.ai/api/experiments/EXPERIMENT_KEY/pricing?userId=USER_ID" \
  -H "x-api-key: YOUR_API_KEY"

# Track a conversion. Send 0 for one that earned nothing; omit revenue to
# record the assigned variant's list price instead.
curl -X POST https://api.last-price.ai/api/experiments/EXPERIMENT_KEY/convert \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"userId": "USER_ID", "revenue": 29.99}'

# Get experiment results
curl https://api.last-price.ai/api/experiments/EXPERIMENT_KEY/results \
  -H "x-api-key: YOUR_API_KEY"

Python Client

import os
import requests

class LastPriceClient:
    # Server side. api_key never reaches a browser.
    def __init__(self, base_url, tenant_id, api_key):
        self.base_url = base_url
        self.tenant_id = tenant_id
        self.headers = {"x-api-key": api_key}

    def get_pricing(self, user_id, experiment_key):
        """Assign a variant and get pricing via the pricing endpoint."""
        resp = requests.get(
            f"{self.base_url}/api/experiments/{experiment_key}/pricing",
            params={"userId": user_id, "tenantId": self.tenant_id},
            headers=self.headers
        )
        resp.raise_for_status()
        return resp.json()

    def track_conversion(self, user_id, experiment_key, revenue=None):
        """Record a conversion.

        Pass revenue=0 for a conversion that earned nothing; it is recorded as
        zero. Leaving it out is what asks for the variant's list price, so the
        key is omitted rather than sent as null, which the API rejects.
        """
        body = {"userId": user_id, "tenantId": self.tenant_id}
        if revenue is not None:
            body["revenue"] = revenue
        resp = requests.post(
            f"{self.base_url}/api/experiments/{experiment_key}/convert",
            json=body,
            headers={**self.headers, "Content-Type": "application/json"}
        )
        resp.raise_for_status()
        return resp.json()

    def get_results(self, experiment_key):
        """Get experiment results."""
        resp = requests.get(
            f"{self.base_url}/api/experiments/{experiment_key}/results",
            params={"tenantId": self.tenant_id},
            headers=self.headers
        )
        resp.raise_for_status()
        return resp.json()

# Usage
lp = LastPriceClient("https://api.last-price.ai", "YOUR_TENANT_ID", os.environ["LASTPRICE_API_KEY"])
result = lp.get_pricing("user_123", "pricing_test")
print(f"Variant: {result['variant']}, Price: ${result['pricing']['price']}")
results = lp.get_results("pricing_test")
print(f"Control conversion rate: {results['control']['conversionRate']}")

Pricing Compute Network (PCN)

The Pricing Compute Network lets you register modular pricing functions, invoke them with rich context, and route requests across multiple strategies - all through a single API.

Pricing Functions

A pricing function encapsulates a single pricing strategy. You can register five types of functions:

  • rule_based - Deterministic rules (e.g., margin tables, tiered pricing)
  • ml_model - Machine-learning model hosted externally or within Last Price
  • llm_prompt - LLM-powered pricing via a prompt template
  • external_api - Delegates to your own REST endpoint
  • composite - Chains or aggregates multiple functions
ParameterTypeRequiredDescription
namestringYesUnique human-readable name
function_typeenumYesOne of: rule_based, ml_model, llm_prompt, external_api, composite
descriptionstringNoShort description of the function's purpose
configobjectYesType-specific configuration (rules, model_url, prompt, endpoint, etc.)
cost_multipliernumberNoScales the metering cost per invocation (default: 1). Use higher values for premium/expensive models.
is_publicbooleanNoWhether to list on the marketplace (default: false)
# Register a new pricing function
curl -X POST https://api.last-price.ai/api/functions \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "margin-lookup-v2",
    "function_type": "rule_based",
    "description": "Applies margin based on product category",
    "config": {
      "rules": [
        { "match": { "product_type": "SaaS" }, "margin": 0.35 },
        { "match": { "product_type": "Hardware" }, "margin": 0.18 }
      ],
      "default_margin": 0.25
    }
  }'
# List your pricing functions
curl https://api.last-price.ai/api/functions \
  -H "Authorization: Bearer YOUR_TOKEN"

# Test a function with sample input
curl -X POST https://api.last-price.ai/api/functions/fn_abc123/test \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "prod_001",
    "product_type": "SaaS",
    "current_price": 99.00,
    "currency": "USD"
  }'

Built-in Functions

The platform ships with 15 pre-registered functions ready to use out of the box - 6 Jale (pricing intelligence), 3 Elo (A/B testing), and 6 Rosetta (data translation). Each has a different cost multiplier reflecting its computational complexity.

FunctionDescriptionMultiplier
lastprice/jale-optimizerStatistical price optimizer2×
lastprice/jale-elasticityPrice elasticity calculator1×
lastprice/jale-advancedAdvanced recommendations with alternatives5×
lastprice/jale-psychologicalPsychological pricing (.99 endings)1×
lastprice/jale-bundleBundle pricing optimizer2×
lastprice/jale-tier-optimizerTiered pricing structure optimizer2×
lastprice/elo-ab-testTwo-arm price split for one call, not recorded (use the Experiments API to track a test)1×
lastprice/elo-significanceCatalog entry, not callable yet1×
lastprice/elo-allocatorCatalog entry, not callable yet2×
lastprice/rosetta-translateTranslate price schemas across platforms1×
lastprice/rosetta-normalizeNormalize price fields to canonical schema1×
lastprice/rosetta-validateValidate schema against canonical contracts1×
lastprice/rosetta-suggestAI-assisted schema mapping suggestions2×
lastprice/rosetta-freezeFreeze a finalized schema adapter1×
lastprice/rosetta-ingestAutonomous merchant onboarding via schema inference3×

The cost_multiplier scales the base compute cost for each invocation. A 5× multiplier means each call costs 5 times the base rate. When a fallback function handles a request, the multiplier of the executed function is used, not the originally requested one.

Compute Price

Submit a single pricing request with full context. The platform selects the appropriate function based on your active routing policy and returns the computed price.

ParameterTypeRequiredDescription
product_idstringYesUnique product identifier
product_typestringNoProduct category (e.g., SaaS, Hardware)
current_pricenumberNoCurrent listed price
currencystringNoISO 4217 currency code (default: USD)
customer_segmentstringNoSegment label (e.g., enterprise, smb)
geostringNoGeographic region code (e.g., US, EU, APAC)
metadataobjectNoArbitrary key-value pairs for custom context
# Compute a single price
curl -X POST https://api.last-price.ai/api/compute/price \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "prod_001",
    "product_type": "SaaS",
    "current_price": 99.00,
    "currency": "USD",
    "customer_segment": "enterprise",
    "geo": "US"
  }'

# Response
{
  "request_id": "req_xyz789",
  "function_id": "fn_abc123",
  "function_name": "margin-lookup-v2",
  "computed_price": 128.70,
  "currency": "USD",
  "confidence": 0.85,
  "explanation": "Applied 35% margin for SaaS category",
  "latency_ms": 42,
  "metering": {
    "input_units": 12,
    "output_units": 8,
    "compute_duration_ms": 42,
    "cost": 0.0023
  }
}

Request Tracing

Compute responses can include a trace with ordered steps, handoffs, and total_ms. Steps describe cache lookup, routing, execution, fallback, constraints, and metering, with a status and duration_ms for each step. A cache hit has cache and meter steps only. Keep the response request_id when investigating a result. See the ComputeTrace schema in the API reference for the complete response contract.

Batch Compute

Process up to 100 pricing requests in a single API call. Each item in the batch is evaluated independently and may route to different functions.

ParameterTypeRequiredDescription
requestsarrayYesArray of pricing request objects (max 100)
fail_fastbooleanNoAbort the entire batch on the first failure (default: false)
max_concurrencynumberNoMaximum number of parallel requests (default: 10)
# Batch compute prices (up to 100 items)
curl -X POST https://api.last-price.ai/api/compute/batch \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requests": [
      {
        "product_id": "prod_001",
        "product_type": "SaaS",
        "current_price": 99.00,
        "currency": "USD"
      },
      {
        "product_id": "prod_002",
        "product_type": "Hardware",
        "current_price": 450.00,
        "currency": "EUR",
        "geo": "EU"
      }
    ],
    "fail_fast": false,
    "max_concurrency": 10
  }'

# Response
{
  "results": [
    {
      "request_id": "req_001",
      "product_id": "prod_001",
      "computed_price": 128.70,
      "currency": "USD",
      "function_id": "fn_abc123"
    },
    {
      "request_id": "req_002",
      "product_id": "prod_002",
      "computed_price": 531.00,
      "currency": "EUR",
      "function_id": "fn_def456"
    }
  ],
  "summary": {
    "total": 2,
    "succeeded": 2,
    "failed": 0,
    "total_cost": 0.0046,
    "total_duration_ms": 120
  }
}

Routing Policies

Routing policies control how the platform selects a pricing function for each request. You can configure strategy, weights, cost/latency constraints, and fallback chains.

cheapest

Select the function with the lowest compute cost

fastest

Select the function with the lowest latency

round_robin

Cycle through functions evenly

weighted

Distribute traffic by configured weights

policy

Rule-based selection from conditions

explicit

Caller specifies the function_id directly

default

Use the tenant's default function

ParameterTypeRequiredDescription
namestringYesPolicy name, up to 255 characters
strategyenumYesRouting strategy (see above)
is_defaultbooleanNoMake this the workspace default. Only the default policy is consulted when a request states no routing_preference
fallback_function_idstringNoSingle fallback function ID. Send null to clear it
configobjectNoEverything below lives inside this object, not at the top level
config.weightsobjectNoFunction ID to weight mapping (for weighted strategy). Keys must be function IDs and at least one weight must be above zero
config.rulesarrayNoArray of condition → function rules (for policy strategy). Each rule needs a non-empty field, a supported operator, and a function_id
config.max_latency_msnumberNoMaximum acceptable latency in ms. Applied when this policy's strategy selects a function
config.max_cost_per_unitnumberNoMaximum cost per compute unit. Applied when this policy's strategy selects a function
config.fallback_chainarrayNoOrdered list of fallback function IDs, used when the strategy yields nothing available
# Create a weighted routing policy with fallback
curl -X POST https://api.last-price.ai/api/routing-policies \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "weighted-with-fallback",
    "strategy": "weighted",
    "is_default": true,
    "config": {
      "weights": {
        "3f1a7c88-0b2e-4d51-9a63-1c4e7b905d12": 0.7,
        "9d2b4e60-7a13-4c8f-b5e2-6f0a83c71d44": 0.3
      },
      "max_latency_ms": 200,
      "fallback_chain": [
        "c7e4a1f2-8d35-4b90-a6c1-2e58f3b74a09",
        "5b8f0d33-6c21-4a7e-9f84-0d1c6e29b537"
      ]
    }
  }'

Circuit Breaker

Each pricing function has an automatic circuit breaker that prevents cascading failures. When a function fails repeatedly, the circuit opens and requests are routed to the fallback chain instead.

Closed

Normal operation - requests flow through to the function

Open

Function is unhealthy - requests are immediately rejected or rerouted to fallbacks

Half-Open

Recovery mode - a limited number of trial requests are allowed to test if the function has recovered

ParameterTypeRequiredDescription
failure_thresholdnumberNoConsecutive failures before opening the circuit (default: 5)
recovery_timeout_msnumberNoMilliseconds to wait before transitioning from open → half-open (default: 30000)
half_open_max_callsnumberNoTrial requests allowed in half-open state (default: 3)
# Create a routing policy with circuit breaker config
curl -X POST https://api.last-price.ai/api/routing-policies \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "explicit-with-breaker",
    "strategy": "explicit",
    "config": {
      "fallback_chain": ["c7e4a1f2-8d35-4b90-a6c1-2e58f3b74a09"],
      "circuit_breaker": {
        "failure_threshold": 3,
        "recovery_timeout_ms": 60000,
        "half_open_max_calls": 2
      }
    }
  }'

Price Router

Price Router is the multi-vendor pricing facade - a single API endpoint in front of the entire catalog of built-in and tenant-owned pricing models. Think of it as the "OpenRouter for price computation": you submit a pricing request and the router selects the best function based on your routing policy, handles metering, and returns the result.

The route mirrors POST /api/compute/price but is the canonical product-named entrypoint for external clients, OpenAPI tooling, and AI-agent tool runtimes. The GET returns a public capability descriptor - no auth needed - so AI agents can introspect the network at planning time.

Overview

The Price Router dashboard at /price-router shows all available routing strategies, the built-in function catalog, and a live test console. It links out to the Routing Policies, Compute, and Functions pages so you don't lose context.

StrategyDescription
cheapestPick the lowest-cost function that can handle the request
fastestPick the function with the lowest median latency
round_robinDistribute requests evenly across eligible functions
weightedSend traffic proportional to per-function weights
explicitUse the specified function_id. A missing, unreachable, or inactive id fails immediately; the fallback chain applies only if the chosen function fails while running
defaultUse the tenant's active routing policy

Route a Request

Submit a pricing request to the router. The request body is identical to POST /api/compute/price.

Naming a function, by id or by name, reaches built-in functions, functions your workspace owns, and functions with a public marketplace listing. Anything else is answered exactly as a function that does not exist, so the response never reveals whether another workspace holds it. The same rule applies to the single compute endpoint and to every item of a batch.

POST /api/price-router
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "function_id": "00000000-0000-0000-0000-000000000001",  // optional UUID - omit to let routing_preference pick
  "routing_preference": "explicit",                        // cheapest | fastest | round_robin | weighted | policy | default
  "context": {
    "product_id":        "prod_001",
    "product_type":      "ecommerce",
    "current_price":     99.00,
    "currency":          "USD",
    "customer_segment":  "enterprise"
  },
  "options": {
    "include_explanation": true,
    "include_confidence":  true
  }
}

// Response
{
  "request_id":    "req_...",
  "function_id":   "00000000-0000-0000-0000-000000000001",
  "function_name": "lastprice/jale-optimizer",
  "computed_price": 109.00,
  "currency":       "USD",
  "confidence":     0.87,
  "explanation":    "Elasticity-adjusted recommendation based on 3-month cohort data",
  "alternatives":   [],
  "metering": { "input_units": 1, "output_units": 1, "compute_duration_ms": 42, "cost": 0.005 },
  "metadata": { "model_version": "1", "routing_reason": "explicit", "cached": false }
}

Capability Descriptor

The public GET /api/price-router returns a static capability document: platform version, all available providers, routing strategies, and the full built-in function catalog. No auth required - suitable for AI-agent tool manifests and external documentation tooling.

GET /api/price-router

// Response (public, cached 5 minutes)
// model_count values are computed dynamically at runtime from the built-in registry
{
  "version": "1.0.0",
  "providers": [
    { "id": "jale",    "display_name": "Jale",    "model_count": 6,
      "description": "Elasticity-driven price optimization, bundle pricing, tier optimization, and psychological-pricing models." },
    { "id": "elo",     "display_name": "Elo",     "model_count": 3,
      "description": "Pricing experimentation: a stable two-arm price split for a single call. Significance testing and bandit allocation are catalog entries, not callable yet. Tracked experiments are the Experiments API." },
    { "id": "rosetta", "display_name": "Rosetta", "model_count": 6,
      "description": "Schema mapping and contract translation between merchant catalogs / vendor APIs and the canonical pricing core." }
  ],
  "strategies": [
    { "id": "explicit",    "description": "Use the function_id supplied in the request. The id must name a built-in function, one this tenant owns, or one with a public marketplace listing; anything else is refused exactly as a function that does not exist. Fails fast if the function is missing, unreachable, or inactive." },
    { "id": "cheapest",    "description": "Pick the active function with the lowest cost_per_unit. Compares the base rate, not the metered cost, so a function with a higher cost_multiplier can still be chosen. As a stored policy strategy it selects only from functions inside that policy config.max_cost_per_unit and config.max_latency_ms, and defers to the policy fallback_chain when none qualify; as a request routing_preference it overrides the policy and neither ceiling applies." },
    { "id": "fastest",     "description": "Pick the active function with the lowest avg_latency_ms. As a stored policy strategy it selects only from functions inside that policy config.max_cost_per_unit and config.max_latency_ms, and defers to the policy fallback_chain when none qualify; as a request routing_preference it overrides the policy and neither ceiling applies." },
    { "id": "round_robin", "description": "Distribute requests evenly across active functions in the catalog. As a stored policy strategy it selects only from functions inside that policy config.max_cost_per_unit and config.max_latency_ms, and defers to the policy fallback_chain when none qualify; as a request routing_preference it overrides the policy and neither ceiling applies." },
    { "id": "weighted",    "description": "Pick a function by weighted random draw using config.weights (function_id to weight) from the tenant default policy. Overrides the strategy that policy declares. With no policy in reach, or no usable weight in it, the draw is an even one over the available functions; a stored weighted policy in that position defers to its fallback chain instead." },
    { "id": "policy",      "description": "Use the tenant default routing policy: config.rules in order, first match wins, then the strategy that policy declares, then its fallback_chain, then its fallback_function_id, then the default selection." },
    { "id": "default",     "description": "Use the tenant default routing policy if set: a matching rule first, then the strategy that policy declares, then its fallback_chain, then its fallback_function_id. With no policy, or when none of those yield a function, prefer a built-in function, then the cheapest." }
  ],
  "builtins": [ /* all built-in function descriptors - count reflects current registry */ ],
  "metering": {
    "cost_per_unit": 0.005,
    "input_factor": 0.001,
    "output_factor": 0.003,
    "formula": "(input_units * cost_per_unit * input_factor + output_units * cost_per_unit * output_factor) * cost_multiplier"
  }
}

Rosetta - Schema Translation

Rosetta solves the hardest integration problem in pricing: every platform uses a different schema. Shopify calls it list_price, Amazon calls it selling_price, your ERP calls it unit_cost.

Rosetta maps any source schema to a canonical schema using AI-assisted planning with hard contracts: the plan is compiled into a vendor contract, validated structurally, and optionally frozen into a sealed adapter that never drifts. Every built-in Rosetta function is also callable as a PCN function (prefix lastprice/rosetta-*) for agent-driven workflows.

API RouteDashboard TabPurpose
POST /api/rosetta/translateTranslateFull pipeline: plan → compile → validate in one call
POST /api/rosetta/planMapping StudioAI-assisted field mapping suggestions from sample data
POST /api/rosetta/normalize-Normalize a vendor response to canonical schema
POST /api/rosetta/freezeFrozen AdaptersPromote a plan to a sealed, versioned adapter
GET/POST /api/rosetta/reviewReview QueueHuman review of low-confidence mapping proposals
POST /api/rosetta/ingestOnboardAutonomous merchant onboarding - infer schema from sample
GET /api/rosetta/canonical-schemasCanonical SchemasList all built-in canonical price schemas

Overview

The pipeline has three stages:

  1. Plan - the heuristic/AI mapper proposes field mappings with per-field confidence scores.
  2. Compile - the plan is turned into a vendor contract (a validated, executable mapping).
  3. Validate - the compiled contract is structurally verified against the canonical schema.

Low-confidence mappings are queued for human review. High-confidence adapters can be frozen into immutable, versioned artifacts that the Commerce Control Plane uses at execution time.

Translate

The translate endpoint runs the full plan → compile → validate pipeline in a single call and returns both the mapping plan and the compiled canonical record.

POST /api/rosetta/translate
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "source_schema": {
    "id": "merchant.product.v1",
    "fields": [
      { "path": "sku", "type": "string" },
      { "path": "list_price", "type": "number" },
      { "path": "currency", "type": "string" }
    ]
  },
  "source_record": { "sku": "BLUE-001", "list_price": 29.99, "currency": "USD" },
  "canonical_schema": "offer",
  "vendor_contract": {
    "id": "lastprice.price.v1",
    "fields": [
      { "path": "sku",           "type": "string",  "required": true },
      { "path": "current_price", "type": "number",  "required": true },
      { "path": "currency",      "type": "string",  "required": false }
    ]
  }
}

// Response
{
  "ready": true,
  "plan": {
    "source_schema_id": "merchant.product.v1",
    "target_schema_id": "lastprice.price.v1",
    "confidence": 0.95,
    "tier": "schema-guided",
    "notes": [],
    "mappings": [
      { "source": "list_price", "target": "current_price", "confidence": 0.97, "origin": "heuristic" },
      { "source": "sku",        "target": "sku",           "confidence": 0.99, "origin": "heuristic" }
    ]
  },
  "payload":    { "sku": "BLUE-001", "current_price": 29.99, "currency": "USD" },
  "skipped":    [],
  "validation": { "valid": true, "issues": [] },
  "review_queued": null
}

Mapping Studio (Plan)

The Mapping Studio lets you draft a mapping from sample data without running the full pipeline. Useful for iterating on schemas before committing. Also agent-callable as lastprice/rosetta-suggest.

POST /api/rosetta/plan
{
  "source_schema": {
    "id": "my.schema.v1",
    "fields": [
      { "path": "list_price", "type": "number" },
      { "path": "currency",   "type": "string" }
    ]
  },
  "canonical_schema": "offer"
}

// Response
{
  "plan": {
    "source_schema_id": "my.schema.v1",
    "target_schema_id": "offer",
    "confidence": 0.88,
    "tier": "schema-guided",
    "notes": [],
    "mappings": [
      { "source": "list_price", "target": "current_price", "confidence": 0.92, "origin": "heuristic" },
      { "source": "currency",   "target": "currency",      "confidence": 0.99, "origin": "heuristic" }
    ]
  },
  "review_queued": null
}

Normalize

Normalize takes a raw vendor API response (after execution) and maps it back to the canonical schema. Use this when you receive a price update confirmation from a connector and need to store it in a standard format.

POST /api/rosetta/normalize
{
  "plan": {
    "sourceSchemaId": "shopify.price.v1",
    "targetSchemaId": "offer",
    "confidence": 0.95,
    "tier": "schema-guided",
    "mappings": [
      { "source": "new_list_price", "target": "current_price", "confidence": 0.96, "origin": "heuristic" },
      { "source": "sku",            "target": "sku",           "confidence": 0.99, "origin": "heuristic" }
    ]
  },
  "vendor_response": { "new_list_price": 34.99, "sku": "BLUE-001" }
}

// Response
{
  "canonical":   { "current_price": 34.99, "sku": "BLUE-001" },
  "missing":     [],
  "lossy":       [],
  "passthrough": {}
}

Freeze Adapter

Freezing promotes a mapping plan to an immutable, versioned adapter. Frozen adapters are used by the Commerce Control Plane at execution time and can be referenced by ID across runs.

POST /api/rosetta/freeze
{
  "plan": {
    "sourceSchemaId": "shopify.price.v1",
    "targetSchemaId": "offer",
    "confidence": 0.97,
    "tier": "schema-guided",
    "mappings": [
      { "source": "list_price", "target": "current_price", "confidence": 0.97, "origin": "heuristic" },
      { "source": "sku",        "target": "sku",           "confidence": 0.99, "origin": "heuristic" }
    ]
  }
}

// Response
{
  "adapter": {
    "sourceSchemaId": "shopify.price.v1",
    "targetSchemaId": "offer",
    "mappings": [
      { "source": "list_price", "target": "current_price", "confidence": 0.97, "origin": "frozen" },
      { "source": "sku",        "target": "sku",           "confidence": 0.99, "origin": "frozen" }
    ],
    "frozenAt": 1747400000000,
    "fingerprint": "a1b2c3d4e5f6"
  }
}

Review Queue

Mappings with low confidence (or those flagged by the validator) are placed in the review queue for human resolution. Reviewers approve or reject each item; approved items can then be frozen.

# List pending review items
GET /api/rosetta/review

# Approve an item
POST /api/rosetta/review
{ "id": "rev_abc", "action": "approve" }

# Reject an item
POST /api/rosetta/review
{ "id": "rev_abc", "action": "reject", "reason": "wrong field mapped" }

Onboard (Ingest)

The Onboard tab provides autonomous merchant onboarding: you paste a sample payload and Rosetta infers the schema, plans the mapping, and proposes a frozen adapter - all in one call. Also callable as the PCN function lastprice/rosetta-ingest for agent-driven onboarding workflows.

POST /api/rosetta/ingest
{
  "canonical_target": "offer",
  "payload": {
    "product_id": "P001",
    "sale_price": 49.99,
    "currency_code": "USD",
    "stock": 12
  },
  "merchant_id": "merchant_abc"
}

// Response - auto-provisioned (confidence ≥ 0.9, adapter frozen immediately)
{
  "status": "auto_provisioned",
  "adapter_id": "ada_...",
  "fingerprint": "f1a2b3c4",
  "canonical_record": { "current_price": 49.99, "currency": "USD" },
  "ready": true,
  "confidence": 0.94,
  "plan": {
    "tier": "schema-guided",
    "notes": [],
    "mappings": [
      { "source": "sale_price",    "target": "current_price", "confidence": 0.95, "origin": "heuristic" },
      { "source": "currency_code", "target": "currency",      "confidence": 0.98, "origin": "heuristic" }
    ]
  }
}
// status values: "stable" | "auto_provisioned" | "provisional" | "needs_review" | "drift_detected"

Marketplace

The Last Price Marketplace allows you to discover, publish, and monetize pricing functions. Publish your own functions to earn revenue when other tenants use them.

Browse Listings

Discover publicly available pricing functions from other providers. Browsing needs no API key: it is rate limited per IP address, and only public listings of active functions appear. Your own listings, private ones included, are ?scope=owned, which needs a key.

# Browse public marketplace listings (no key needed)
curl "https://api.last-price.ai/api/marketplace/listings?limit=20&offset=0"

# Your own listings need a key
curl "https://api.last-price.ai/api/marketplace/listings?scope=owned" \
  -H "x-api-key: YOUR_API_KEY"

# Response (public scope)
{
  "listings": [
    {
      "function": {
        "id": "8b1c...",
        "name": "acme/saas-pricer",
        "display_name": "Dynamic SaaS Pricer",
        "execution_type": "ml_model",
        "cost_per_unit": 0.002,
        "is_builtin": false
      },
      "listing": { "id": "4f2e...", "is_public": true, "commission_rate": 0.15 },
      "stats": {
        "total_invocations": 124500,
        "distinct_consumers": 38,
        "usage_status": "published",
        "avg_latency_ms": 38,
        "avg_confidence": 0.85
      }
    }
  ],
  "pagination": { "limit": 20, "offset": 0, "total": 47 }
}

Publish Functions

List your pricing functions on the marketplace. Set a commission rate and provide a description so other users can discover and subscribe to your function.

# Publish a function to the marketplace
curl -X POST https://api.last-price.ai/api/marketplace/listings \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "function_id": "fn_abc123",
    "commission_rate": 0.10,
    "category": "SaaS",
    "description": "Enterprise SaaS pricing with geo-aware margins"
  }'

Earnings

Track revenue earned from your published marketplace functions. View totals, breakdowns per function, and historical trends.

# View marketplace earnings
curl https://api.last-price.ai/api/marketplace/earnings \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -G \
  -d "period=last_30d"

# Response
{
  "total_earned": 1284.50,
  "currency": "USD",
  "period": "last_30d",
  "by_function": [
    { "function_id": "fn_abc123", "name": "margin-lookup-v2", "earned": 892.30 },
    { "function_id": "fn_def456", "name": "geo-pricer", "earned": 392.20 }
  ]
}

Payouts

View your payout history and current payout status. Payouts are processed automatically via Stripe Connect on a configurable schedule.

# List payout history
curl https://api.last-price.ai/api/marketplace/payouts \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{
  "payouts": [
    {
      "payout_id": "po_001",
      "amount": 892.30,
      "currency": "USD",
      "status": "paid",
      "paid_at": "2024-12-01T00:00:00Z"
    },
    {
      "payout_id": "po_002",
      "amount": 1284.50,
      "currency": "USD",
      "status": "pending",
      "estimated_at": "2025-01-01T00:00:00Z"
    }
  ]
}

Usage & Billing

Monitor your platform consumption, view granular event logs, and manage Stripe-based billing - all from a single set of endpoints.

Usage Summary

Retrieve an aggregated summary of total requests, compute time, and costs for a given period.

ParameterTypeRequiredDescription
start_datestring (ISO 8601)YesStart of the period
end_datestring (ISO 8601)YesEnd of the period
granularityenumNodaily, weekly, or monthly (default: daily)
# Get usage summary
curl https://api.last-price.ai/api/usage \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -G \
  -d "start_date=2024-12-01" \
  -d "end_date=2024-12-31" \
  -d "granularity=daily"

# Response
{
  "total_requests": 48210,
  "total_compute_ms": 1932840,
  "total_cost": 142.38,
  "currency": "USD",
  "period": { "start": "2024-12-01", "end": "2024-12-31" }
}

Usage Events

Access the per-request granular event log. Each event contains the function invoked, latency, cost, input context, and computed result.

# List usage events (paginated)
curl https://api.last-price.ai/api/usage/events \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -G \
  -d "start_date=2024-12-01" \
  -d "page=1" \
  -d "per_page=50"

# Response
{
  "events": [
    {
      "event_id": "evt_001",
      "timestamp": "2024-12-15T10:32:01Z",
      "function_id": "fn_abc123",
      "product_id": "prod_001",
      "latency_ms": 38,
      "cost": 0.003,
      "result": { "computed_price": 128.70 }
    }
  ],
  "total": 48210,
  "page": 1
}

Usage Breakdown

View a function-level breakdown of usage. Identify which pricing functions consume the most resources and generate the most traffic.

# Get function-level usage breakdown
curl https://api.last-price.ai/api/usage/breakdown \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -G \
  -d "start_date=2024-12-01" \
  -d "end_date=2024-12-31"

# Response
{
  "breakdown": [
    {
      "function_id": "fn_abc123",
      "function_name": "margin-lookup-v2",
      "requests": 32140,
      "avg_latency_ms": 35,
      "total_cost": 96.42
    },
    {
      "function_id": "fn_def456",
      "function_name": "geo-pricer",
      "requests": 16070,
      "avg_latency_ms": 62,
      "total_cost": 45.96
    }
  ]
}

AI Usage

Usage of the AI-backed features (agent generation, task estimation). Reported separately from pricing functions, and split by which key paid for each call: calls served with your own provider key are not billed by Last Price, so platform_key_cost is the billable amount.

A date-only range is normalised before it is queried, so the echoed period comes back as UTC instants and the end covers the whole final day. Fractional seconds are accepted to microsecond precision and preserved exactly.

# Get AI feature usage for the current month
curl https://api.last-price.ai/api/usage/llm \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -G \
  -d "start_date=2024-12-01" \
  -d "end_date=2024-12-31"

# Response
{
  "tenant_id": "3f2b1c88-5d4e-4a91-9c2f-7b6a0d1e8f42",
  "period": {
    "start": "2024-12-01T00:00:00.000000Z",
    "end": "2024-12-31T23:59:59.999999Z"
  },
  "usage": {
    "total_calls": 128,
    "total_input_tokens": 98400,
    "total_output_tokens": 24100,
    "total_compute_ms": 184000,
    "total_cost": 3.87,
    "tenant_key_calls": 90,
    "platform_key_calls": 38,
    "platform_key_cost": 1.12,
    "by_operation": [
      {
        "operation": "llm.agent-generation",
        "calls": 74,
        "input_tokens": 61200,
        "output_tokens": 15300,
        "compute_duration_ms": 110400,
        "cost": 2.41
      },
      {
        "operation": "llm.task-estimation",
        "calls": 54,
        "input_tokens": 37200,
        "output_tokens": 8800,
        "compute_duration_ms": 73600,
        "cost": 1.46
      }
    ]
  }
}

Usage Signals

Record custom signals for analytics and observability. Signals can track conversion rates, A/B test outcomes, or any custom metric you define.

# Record a custom usage signal
curl -X POST https://api.last-price.ai/api/usage/signals \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "price_accepted",
    "properties": {
      "product_id": "prod_001",
      "computed_price": 128.70,
      "customer_segment": "enterprise"
    }
  }'

Stripe Integration

Last Price integrates natively with Stripe for metered billing, invoicing, and marketplace payouts. Usage is automatically reported to Stripe and reflected on your monthly invoice.

Metered Billing

Usage automatically synced to Stripe meter events

Invoicing

Monthly invoices generated and sent via Stripe

Marketplace Payouts

Earnings paid out via Stripe Connect

Payment Methods

Cards, ACH, and wire supported through Stripe

Authentication & Security

If you are calling the API from your own code, you have two credentials: API keys (long-lived, ideal for server-to-server) and JWT Bearer tokens (short-lived, minted for agents from an agent-bound key). Every secured endpoint takes one of these. The credential exchanges are the exception, since they carry their own credential: /api/oauth/token takes client credentials, and native apps call /api/auth/native-token with their Supabase access token to get a Last Price JWT. Two other credentials exist but are not for API callers: webhook receivers authenticate the sender by signature rather than by key, such as x-paid-signature on /api/webhooks/paid, and the dashboard authenticates with its own session cookie, which is what the settings pages use. Explore all endpoints interactively in the API Reference.

JWT Tokens

This section covers the agent JWT. Native apps get a JWT too, from the Supabase exchange at /api/auth/native-token described above, which carries the signed-in user rather than an agent. Exchange an agent-bound API key for a one-hour token with the OAuth 2.0 client credentials grant, then send it in the Authorization header as a Bearer token. The client_id is the key prefix and the client_secret is the full key. Plain tenant keys are not exchanged: they authenticate directly as x-api-key.

# Exchange an agent-bound key for a JWT
curl -X POST https://api.last-price.ai/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=lp_a3f9e2b1" \
  -d "client_secret=lp_a3f9e2b1c4d5e6f7..."

# Response
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "price:compute commerce:catalog:read",
  "agent_id": "550e8400-e29b-41d4-a716-446655440000"
}

# Use the token
curl https://api.last-price.ai/api/functions \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

API Keys

For server-to-server integrations, use long-lived API keys. Keys are prefixed with lp_ and stored as SHA-256 hashes; the plaintext is returned only once at creation. Optionally restrict a key to specific scopes (e.g., price:compute, billing:read, or a wildcard such as commerce:*) for least-privilege access. Any other name is refused with a 400 that lists the valid scopes, which /.well-known/agents.json also publishes. Keys created earlier with the old read, write or admin labels keep their billing and commerce access. Manage keys via Settings → API Keys in the dashboard, or directly via the API.

# Create a key with scopes (requires JWT)
curl -X POST https://api.last-price.ai/api/api-keys \
  -H "Authorization: Bearer <JWT>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production server", "scopes": ["price:compute", "pcn:function:read"]}'

# Response (key shown only once, save it now)
{
  "api_key": {
    "id": "550e8400-...",
    "name": "Production server",
    "key": "lp_a3f9e2b1c4d5e6f7...",
    "key_prefix": "lp_a3f9e2",
    "scopes": ["price:compute", "pcn:function:read"],
    "created_at": "2025-01-01T00:00:00Z",
    "expires_at": null
  }
}

# Use the key in subsequent requests
curl https://api.last-price.ai/api/functions \
  -H "x-api-key: lp_a3f9e2b1c4d5e6f7..."

# List all active keys
curl https://api.last-price.ai/api/api-keys \
  -H "Authorization: Bearer <JWT>"

# Revoke a key
curl -X DELETE https://api.last-price.ai/api/api-keys/<keyId> \
  -H "Authorization: Bearer <JWT>"

Rotate an API Key

POST /api/api-keys/rotate with the current key in x-api-key replaces that key. Save api_key.key from the 201 response immediately: it is shown once, and the old key stops working. The replacement preserves scopes, agent binding, name, and expiry. Rotation does not extend a sandbox key's lifetime or increase its permissions. Sandbox rotation limits still apply. Use Settings, API keys to manage keys with a browser session.

Legacy Authentication

The x-tenant-id header is rejected on all secured endpoints. Use Authorization: Bearer <JWT> or x-api-key: lp_... instead.

# ❌ Rejected - x-tenant-id no longer accepted on secured endpoints
curl https://api.last-price.ai/api/customers \
  -H "x-tenant-id: 00000000-0000-0000-0000-000000000001"
# → 401 Unauthorized

# ✅ Use JWT Bearer
curl https://api.last-price.ai/api/customers \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

# ✅ Or use an API key (lp_ prefix)
curl https://api.last-price.ai/api/customers \
  -H "x-api-key: lp_a3f9e2b1c4d5e6f7..."

Rate Limiting

All API endpoints are rate-limited per tenant using a sliding window algorithm. When you exceed the limit, the API returns a 429 Too Many Requests response.

ParameterTypeRequiredDescription
X-RateLimit-LimitheaderNoMaximum requests allowed in the window
X-RateLimit-RemainingheaderNoRemaining requests in the current window
X-RateLimit-ResetheaderNoUnix timestamp when the window resets
Retry-AfterheaderNoSeconds to wait before retrying (on 429)
# Rate limit headers in every response
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1704067200

# When rate limited
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Retry after 30 seconds."
}

Webhooks

Receive real-time event notifications via webhooks. Every webhook delivery includes an HMAC-SHA256 signature in the x-paid-signature header, the event type in x-webhook-event, and a Unix timestamp in x-paid-timestamp.

# Register a webhook endpoint (an owner or admin of the workspace)
curl -X POST https://api.last-price.ai/api/webhooks \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/last-price",
    "events": ["balance.low", "platform.changelog", "rosetta.adapter.review_required"]
  }'
# The response carries the signing secret (whsec_...). Send a test event with
# POST /api/webhooks/{webhookId}/ping.

# Verify the signature in your handler: HMAC-SHA256 of the raw body, hex encoded
import hmac, hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Core Business Objects

Last Price provides a full suite of business objects for managing your commercial operations. All objects support standard CRUD operations and are accessible via RESTful endpoints.

Customers

Manage customer records including billing details, segments, and metadata.

# Create a customer
curl -X POST https://api.last-price.ai/api/customers \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "email": "billing@acme.com",
    "externalId": "acme_123",
    "metadata": { "industry": "technology" }
  }'

# Response (201)
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "external_id": "acme_123",
  "name": "Acme Corp",
  "email": "billing@acme.com",
  "metadata": { "industry": "technology" },
  "created_at": "2026-01-15T14:30:00.000Z",
  "updated_at": "2026-01-15T14:30:00.000Z"
}

# List customers
curl "https://api.last-price.ai/api/customers?limit=25&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{
  "data": [ /* customer rows */ ],
  "pagination": { "limit": 25, "offset": 0, "total": 1 }
}

# Look one up by your own identifier
curl https://api.last-price.ai/api/customers/external/acme_123 \
  -H "Authorization: Bearer YOUR_TOKEN"

Contacts

A contact is a person on a customer account: who an invoice is addressed to, and who an order can name as its billing contact. A contact belongs to exactly one customer and cannot be moved between them.

# Create a contact
curl -X POST https://api.last-price.ai/api/contacts \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "name": "Jane Doe",
    "email": "jane.doe@acme.com",
    "phone": "+1-555-0123",
    "externalId": "acme_contact_123",
    "address": { "line1": "123 Business Ave", "city": "San Francisco", "country": "USA" }
  }'

# Response (201)
{
  "id": "3f1a9c88-2b47-4e0d-9f0a-6c2f7d5b1e34",
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "external_id": "acme_contact_123",
  "name": "Jane Doe",
  "email": "jane.doe@acme.com",
  "phone": "+1-555-0123",
  "address": { "line1": "123 Business Ave", "city": "San Francisco", "country": "USA" },
  "metadata": {},
  "created_at": "2026-01-15T14:30:00.000Z",
  "updated_at": "2026-01-15T14:30:00.000Z"
}

# List one customer's contacts
curl "https://api.last-price.ai/api/contacts?customer_id=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Update one. Only the properties you send are written; send null to clear
# externalId, phone or address.
curl -X PATCH https://api.last-price.ai/api/contacts/3f1a9c88-2b47-4e0d-9f0a-6c2f7d5b1e34 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "phone": null }'

# Look one up by your own identifier. External IDs are unique per customer,
# so the customer has to come with it.
curl "https://api.last-price.ai/api/contacts/external/acme_contact_123?customerId=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Delete one. An order billing this contact keeps its own row and has
# billing_contact_id cleared.
curl -X DELETE https://api.last-price.ai/api/contacts/3f1a9c88-2b47-4e0d-9f0a-6c2f7d5b1e34 \
  -H "Authorization: Bearer YOUR_TOKEN"

Orders

Create and manage orders. Orders can contain multiple line items and track fulfillment status. An order that has started billing cannot be deleted outright: cancel it first, then delete it.

# Create an order. customerId is a customer UUID.
# Line items are added afterwards, one request each.
curl -X POST https://api.last-price.ai/api/orders \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "totalAmount": 643.50,
    "currency": "USD",
    "metadata": { "channel": "self-serve" }
  }'

# Response (201). Money columns come back as decimal strings, not numbers.
{
  "id": "9b2d4f61-0c3a-4e58-8d77-1f5a6b9c0e23",
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "agent_id": null,
  "status": "draft",
  "total_amount": "643.5000",
  "currency": "USD",
  "metadata": { "channel": "self-serve" },
  "created_at": "2026-01-15T14:30:00.000Z",
  "updated_at": "2026-01-15T14:30:00.000Z"
}

# Add a line, one request per line
curl -X POST https://api.last-price.ai/api/orders/9b2d4f61-0c3a-4e58-8d77-1f5a6b9c0e23/lines \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Pro plan, January", "quantity": 2, "unitPrice": 19.99 }'

# Activate it
curl -X POST https://api.last-price.ai/api/orders/9b2d4f61-0c3a-4e58-8d77-1f5a6b9c0e23/activate \
  -H "Authorization: Bearer YOUR_TOKEN"

# List orders, optionally for one customer
curl "https://api.last-price.ai/api/orders?limit=25&offset=0&customer_id=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{
  "data": [ /* order rows */ ],
  "pagination": { "limit": 25, "offset": 0, "total": 1 }
}

# List the line items on an order, oldest first
curl https://api.last-price.ai/api/orders/9b2d4f61-0c3a-4e58-8d77-1f5a6b9c0e23/lines \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{ "data": [ /* order line rows */ ] }

# Cancel an order. This is also how you make one deletable.
curl -X PATCH https://api.last-price.ai/api/orders/9b2d4f61-0c3a-4e58-8d77-1f5a6b9c0e23 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "cancelled" }'

# Delete a draft or cancelled order, and its line items with it.
# Any other status answers 409 and tells you to cancel first.
curl -X DELETE https://api.last-price.ai/api/orders/9b2d4f61-0c3a-4e58-8d77-1f5a6b9c0e23 \
  -H "Authorization: Bearer YOUR_TOKEN"

Invoices

Generate and manage invoices with line items, tax calculations, and due dates. Invoices can be synced to Stripe for automated collection.

# Create an invoice
curl -X POST https://api.last-price.ai/api/invoices \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "invoiceNumber": "INV-2026-0001",
    "amount": 643.50,
    "currency": "USD",
    "dueDate": "2026-02-01T00:00:00.000Z"
  }'

# Response (201). The amount comes back as a decimal string, not a number.
{
  "id": "c5a7e310-84bd-4f62-9f0a-2d7c1b8e4a56",
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "invoice_number": "INV-2026-0001",
  "status": "draft",
  "amount": "643.5000",
  "currency": "USD",
  "due_date": "2026-02-01T00:00:00.000Z",
  "metadata": {},
  "created_at": "2026-01-15T14:30:00.000Z",
  "updated_at": "2026-01-15T14:30:00.000Z"
}

# Issue it, and email it to the customer when delivery is configured
curl -X POST https://api.last-price.ai/api/invoices/c5a7e310-84bd-4f62-9f0a-2d7c1b8e4a56/send \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response: the invoice row plus the delivery outcome
{
  "id": "c5a7e310-84bd-4f62-9f0a-2d7c1b8e4a56",
  "status": "sent",
  "message": "Invoice issued and emailed to the customer.",
  "email": { "delivered": true, "skipped": false }
}

# List invoices, optionally for one customer
curl "https://api.last-price.ai/api/invoices?limit=25&offset=0&customer_id=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{
  "data": [ /* invoice rows */ ],
  "pagination": { "limit": 25, "offset": 0, "total": 1 }
}

Payments

Record and track payments against invoices. Supports partial payments and multiple payment methods.

# Record a payment
curl -X POST https://api.last-price.ai/api/payments \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "invoiceId": "c5a7e310-84bd-4f62-9f0a-2d7c1b8e4a56",
    "amount": 643.50,
    "currency": "USD",
    "status": "completed",
    "paymentMethod": "card",
    "metadata": { "reference": "ch_abc123" }
  }'

# Response (201). The amount comes back as a decimal string, not a number.
{
  "id": "e81f0d45-6a2c-4b93-8177-0c9d3e5f2a68",
  "invoice_id": "c5a7e310-84bd-4f62-9f0a-2d7c1b8e4a56",
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "amount": "643.5000",
  "currency": "USD",
  "status": "completed",
  "payment_method": "card",
  "metadata": { "reference": "ch_abc123" },
  "created_at": "2026-01-15T14:30:00.000Z",
  "updated_at": "2026-01-15T14:30:00.000Z"
}

# Refund it, in full or in part
curl -X POST https://api.last-price.ai/api/payments/e81f0d45-6a2c-4b93-8177-0c9d3e5f2a68/refund \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 100.00, "reason": "Partial credit" }'

# Response: the payment row plus a confirmation
{
  "id": "e81f0d45-6a2c-4b93-8177-0c9d3e5f2a68",
  "status": "refunded",
  "metadata": { "refund": { "amount": 100, "reason": "Partial credit" } },
  "message": "Payment refunded successfully"
}

# List payments
curl "https://api.last-price.ai/api/payments?limit=25&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{
  "data": [ /* payment rows */ ],
  "pagination": { "limit": 25, "offset": 0, "total": 1 }
}

Disputes

Manage payment disputes and chargebacks. Track status, submit evidence, and monitor resolutions.

# Open a dispute against a payment
curl -X POST https://api.last-price.ai/api/disputes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentId": "e81f0d45-6a2c-4b93-8177-0c9d3e5f2a68",
    "amount": 643.50,
    "currency": "USD",
    "reason": "product_not_received"
  }'

# List disputes
curl "https://api.last-price.ai/api/disputes?limit=25&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response. The amount comes back as a decimal string, not a number.
{
  "data": [
    {
      "id": "1d6b8f23-9e04-47ac-b5f1-3a8c0d2e7b94",
      "payment_id": "e81f0d45-6a2c-4b93-8177-0c9d3e5f2a68",
      "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "amount": "643.5000",
      "currency": "USD",
      "status": "open",
      "reason": "product_not_received",
      "metadata": {},
      "created_at": "2026-01-15T14:30:00.000Z",
      "updated_at": "2026-01-15T14:30:00.000Z"
    }
  ],
  "pagination": { "limit": 25, "offset": 0, "total": 1 }
}

# Move it on. Valid states: open, under_review, resolved, rejected
curl -X PATCH https://api.last-price.ai/api/disputes/1d6b8f23-9e04-47ac-b5f1-3a8c0d2e7b94 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "resolved" }'

Credits

Issue credits to customer accounts. Credits can be applied to future invoices automatically or manually.

# Issue a credit bundle
curl -X POST https://api.last-price.ai/api/credits \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "amount": 50.00,
    "currency": "USD",
    "expiresAt": "2026-12-31T23:59:59.000Z",
    "metadata": { "reason": "Service disruption compensation" }
  }'

# Response (201). Amount and balance come back as decimal strings.
{
  "id": "a3c05e78-1b94-4d26-8f50-6e7b2c9a1d43",
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "amount": "50.0000",
  "balance": "50.0000",
  "currency": "USD",
  "expires_at": "2026-12-31T23:59:59.000Z",
  "metadata": { "reason": "Service disruption compensation" },
  "created_at": "2026-01-15T14:30:00.000Z",
  "updated_at": "2026-01-15T14:30:00.000Z"
}

# View a customer's balance, summed over the bundles that have not expired
curl "https://api.last-price.ai/api/credits/balance?customerId=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response: one row per currency, plus those rows added together
{
  "customerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "balances": [
    {
      "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "total_balance": "50.0000",
      "currency": "USD",
      "bundle_count": "1"
    }
  ],
  "totalBalance": 50
}

Agents

Agents represent AI-powered pricing bots that automate pricing workflows. Each agent can have its own pricing model, metadata, and optional external identifier for integration with your systems.

Create Agent

Register a new agent with a name, optional description, pricing model, and custom metadata.

ParameterTypeRequiredDescription
namestringYesHuman-readable agent name
externalIdstringNoYour own identifier for cross-system lookups
descriptionstringNoShort description of the agent's purpose
pricingModelstringNoPricing model identifier to use
metadataobjectNoArbitrary key-value pairs for custom data
# Create a new agent
curl -X POST https://api.last-price.ai/api/agents \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dynamic Pricer",
    "description": "Adjusts SaaS pricing based on usage patterns",
    "externalId": "agent-prod-001",
    "pricingModel": "usage_based",
    "metadata": {
      "tier": "enterprise",
      "region": "us-east"
    }
  }'

# Response (201)
{
  "id": "a1b2c3d4-...",
  "external_id": "agent-prod-001",
  "name": "Dynamic Pricer",
  "description": "Adjusts SaaS pricing based on usage patterns",
  "pricing_model": "usage_based",
  "metadata": { "tier": "enterprise", "region": "us-east" },
  "created_at": "2025-01-10T09:00:00Z",
  "updated_at": "2025-01-10T09:00:00Z"
}

Manage Agents

List, retrieve, update, or delete agents. Pagination is supported via limit (max 1000) and offset query parameters.

# List agents (paginated)
curl "https://api.last-price.ai/api/agents?limit=50&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Get a single agent
curl https://api.last-price.ai/api/agents/AGENT_ID \
  -H "Authorization: Bearer YOUR_TOKEN"

# Update an agent
curl -X PATCH https://api.last-price.ai/api/agents/AGENT_ID \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Pricer",
    "metadata": { "tier": "premium" }
  }'

# Delete an agent
curl -X DELETE https://api.last-price.ai/api/agents/AGENT_ID \
  -H "Authorization: Bearer YOUR_TOKEN"

Signals

Signals is the behavioral data layer for pricing intelligence. It captures every meaningful event - agent pricing decisions, inference calls, conversion outcomes, customer interactions - and surfaces them as a queryable stream. The Commerce Control Plane, Jale, and Elo all emit signals automatically; you can also push custom signals via the API.

Overview

Navigate to /signals in the dashboard to browse the signal stream. Toggle Raw signalsto see unprocessed events. Filter by time range, agent, customer, or event type. Signals feed into Jale's optimization loop and are available via the usage-signals API endpoint.

Signal typeEmitted byUse
price_computedPCN / Price RouterEvery routed inference call
variant_assignedEloA/B test bucket assignment
conversionElo / Snippet / APIRevenue event attached to a variant
proposal_createdCommerce CPPricing policy produced a proposal
proposal_executedCommerce CPPrice pushed to connector
agent_actionAgentsAny agent-driven pricing decision
customYour codeAny domain event you push via the API

Agent Pricing Signals

AI agents that call the Price Router or PCN can record price_computed and agent_action signals via POST /api/usage/signals. The Agent Pricing Signals dashboard (/signals) lets you drill into per-agent decision histories, see which pricing models were used, and correlate outcomes with revenue.

# Record a signal from an agent pricing action
POST /api/usage/signals
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "eventType":  "price_computed",
  "agentId":    "4f1b8a91-2e07-44b6-9a3a-1b8c2e72b001",
  "customerId": "0b6c7d1e-5f43-4a8e-9d2b-3c1f6e8a7b90",
  "properties": {
    "function_used": "lastprice/jale-optimizer",
    "input_price":   49.99,
    "output_price":  54.99,
    "confidence":    0.91,
    "latency_ms":    230
  },
  "metadata": {}
}

// Response (201 Created)
{
  "id": "9d3e2c71-6a4b-4f8e-b1c2-7e5d8a9f0b13",
  "message": "Usage signal recorded successfully",
  "signal": {
    "id": "9d3e2c71-6a4b-4f8e-b1c2-7e5d8a9f0b13",
    "tenant_id": "2a7c9e14-3b5d-4c6f-8e1a-6d4b2f9c7e05",
    "agent_id":    "4f1b8a91-2e07-44b6-9a3a-1b8c2e72b001",
    "customer_id": "0b6c7d1e-5f43-4a8e-9d2b-3c1f6e8a7b90",
    "event_type":  "price_computed",
    "properties":  { "function_used": "lastprice/jale-optimizer", "output_price": 54.99 },
    "metadata":    {},
    "created_at":  "2025-01-15T10:23:00.000Z"
  }
}

Experiments & A/B Testing

Run pricing experiments across your products. Define variants with different price points, assign users deterministically, track conversions, and view statistical results - all through a simple API.

Create Experiment

Create a new experiment with a unique key and exactly two pricing variants, namedcontrol and experiment. Those are the only names the assignment engine serves, so any other name, or a third arm, is rejected. Each variant can carry custom metadata (e.g., features, discount percentages) that gets forwarded to clients.

ParameterTypeRequiredDescription
keystringYesUnique key (lowercase alphanumeric + underscores)
namestringYesHuman-readable experiment name
descriptionstringNoDescription of what is being tested
variantsarrayYesExactly two variants: the engine is two-armed
variants[].namestringYesMust be 'control' or 'experiment', lowercase. Any other name is rejected
variants[].pricenumberYesPrice for this variant (≥ 0)
variants[].weightnumberNoRelative traffic weight, normalised against the other arm, so 70/30 and 0.7/0.3 are the same split. Set both or neither (default: equal). The engine resolves traffic to 1%, so neither arm may work out below that: 1/99 is the most uneven split accepted
variants[].metadataobjectNoArbitrary data forwarded to clients
targetSampleSizenumberNoTarget number of users, stored with the experiment and returned with it. Nothing completes an experiment automatically yet, so this records your intent rather than triggering anything
# Discover your tenant first. Do not type a UUID: the tenant in
# the path must match the one your credential resolves to, or it is a 403.
curl https://api.last-price.ai/api/me -H "Authorization: Bearer YOUR_TOKEN"
# => { "tenant_id": "...", ... }   Use that value as TENANT_ID below.

# Create a pricing experiment. A JWT goes in Authorization; an API key goes
# in x-api-key and is not accepted as a bearer token. Send one or the other.
curl -X POST https://api.last-price.ai/api/tenants/TENANT_ID/experiments \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "homepage_pricing",
    "name": "Homepage Pricing Test",
    "description": "Testing $29 vs $39 price points",
    "variants": [
      {
        "name": "control",
        "price": 29,
        "weight": 0.7,
        "metadata": {
          "plan": "Pro",
          "features": ["Unlimited projects", "Priority support"]
        }
      },
      {
        "name": "experiment",
        "price": 39,
        "weight": 0.3,
        "metadata": {
          "plan": "Pro Plus",
          "features": ["Unlimited projects", "Priority support", "API access"]
        }
      }
    ],
    "targetSampleSize": 1000
  }'

# List experiments
curl "https://api.last-price.ai/api/tenants/TENANT_ID/experiments?status=active" \
  -H "Authorization: Bearer YOUR_TOKEN"

Get Pricing

Retrieve the pricing for a specific user. The system deterministically assigns the user to a variant and returns the full pricing payload including any variant metadata. The path parameter is the experiment key you chose, such as homepage_pricing, not the UUID of the experiment record.

# From your server, with your secret API key
curl "https://api.last-price.ai/api/experiments/homepage_pricing/pricing?userId=user_abc123" \
  -H "x-api-key: lp_YOUR_API_KEY"

# From a web page, the snippet sends a publishable embed token instead
#   x-embed-token: lpe_YOUR_EMBED_TOKEN

# Response
{
  "userId": "user_abc123",
  "experimentId": "homepage_pricing",
  "variant": "control",
  "pricing": {
    "plan": "Control",
    "price": 29,
    "features": ["Feature A", "Feature B", "Feature C"]
  }
}

The pricingobject includes the variant's plan, price, features, and any additional metadata fields from the variant definition.

Track Conversions

Record a conversion event when a user completes a purchase or desired action. Send the amount you actually settled, including 0 for a free trial activation or a fully discounted order: an explicit zero is recorded as zero. Omitting revenueis what asks for the assigned variant's list price instead. The tenant comes from your credential; the optional tenantId in the body is only checked against it, and a mismatch is rejected.

# From your server, with your secret API key. A browser sends
# x-embed-token instead; the snippet does this for you.
curl -X POST https://api.last-price.ai/api/experiments/homepage_pricing/convert \
  -H "x-api-key: lp_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user_abc123",
    "revenue": 29.00
  }'

# Response
{
  "success": true,
  "userId": "user_abc123",
  "experimentId": "homepage_pricing",
  "variant": "control",
  "revenue": 29
}

View Results

Retrieve experiment performance data including per-variant views, conversions, conversion rates, and revenue metrics. Use the definition endpoint for the full experiment configuration.

# Get experiment results. Server side only: an embed token does not
# reach these, and there is no cross-origin access to them.
curl https://api.last-price.ai/api/experiments/homepage_pricing/results \
  -H "x-api-key: lp_YOUR_API_KEY"

# Get experiment definition
curl https://api.last-price.ai/api/experiments/homepage_pricing/definition \
  -H "x-api-key: lp_YOUR_API_KEY"

# Results response
{
  "experimentId": "EXPERIMENT_ID",
  "control": {
    "views": 523,
    "conversions": 47,
    "revenue": "1363.00",
    "conversionRate": "8.99%",
    "arpu": "29.00"
  },
  "experiment": {
    "views": 477,
    "conversions": 31,
    "revenue": "1209.00",
    "conversionRate": "6.50%",
    "arpu": "39.00"
  }
}

Jale - AI Price Optimizer

Jale is the built-in AI pricing optimization engine. It analyzes experiment data to recommend optimal prices and can automatically propose new variants based on conversion performance.

Optimize Pricing

Submit an optimization request to get AI-powered pricing recommendations for an active experiment. Optionally specify an objective (e.g., maximize revenue or conversions) and candidate price points. Invalid candidate prices return 400. Missing experiments return 404. Unavailable or malformed experiment data returns 502 without a recommendation.

ParameterTypeRequiredDescription
experimentIdstringYesExperiment to optimize
objectivestringNoOptimization goal (e.g., 'maximize_revenue', 'maximize_conversions')
candidatesarrayNoCandidate price points to evaluate
lookbackDaysnumberNoDays of historical data to consider
# Get pricing optimization recommendation
curl -X POST https://api.last-price.ai/api/jale/optimize \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "experimentId": "homepage_pricing",
    "objective": "maximize_revenue",
    "candidates": [19, 24, 29, 34, 39],
    "lookbackDays": 30
  }'

Propose Variant

Not usable yet. This would add a third variant to an existing experiment, but the assignment engine is two-armed and a valid experiment already holds both arms, so every proposal is rejected with a 400 rather than stored where it could never be served. The endpoint becomes useful when multi-variant testing lands. Documented here so the behaviour is not a surprise.

ParameterTypeRequiredDescription
experimentIdstringYesTarget experiment
pricenumberYesPrice point for the new variant (must be greater than 0)
labelstringNoHuman-readable variant name (auto-generated if omitted)
metadataobjectNoCustom data for the variant
# Propose a new variant
curl -X POST https://api.last-price.ai/api/jale/propose-variant \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "experimentId": "homepage_pricing",
    "price": 34,
    "label": "mid_tier_test",
    "metadata": {
      "plan": "Pro",
      "features": ["Unlimited projects", "Priority support", "Custom branding"]
    }
  }'

# Response today. A valid experiment already holds both arms the engine can
# serve, so this proposal would add a third that is never assigned.
{
  "error": "Cannot propose an additional variant",
  "message": "Exactly 2 variants are required, named control and experiment. Received 3. The assignment engine is two-armed, so any additional variant would never be served. Multi-variant testing is not supported yet."
}

JavaScript SDK

A lightweight (~4 KB minified, ~1.5 KB gzipped) vanilla JavaScript SDK for embedding A/B tested pricing on any website. Zero dependencies - works with any framework or static site.

Installation

Include the SDK via a script tag. You can either configure it programmatically or use data attributes for automatic initialization.

<!-- Option 1: Script tag with manual configuration -->
<div id="pricing-container"></div>
<script src="https://last-price.ai/lastprice.js"></script>
<script>
  LastPrice.configure({
    apiBase: 'https://api.last-price.ai',
    embedToken: 'lpe_YOUR_EMBED_TOKEN',
    tenantId: 'YOUR_TENANT_ID',
    experimentId: 'homepage_pricing'
  });
  LastPrice.showPricing('#pricing-container');
</script>

<!-- Option 2: Data attributes (auto-load) -->
<div id="pricing-container"></div>
<script
  src="https://last-price.ai/lastprice.js"
  data-api-base="https://api.last-price.ai"
  data-embed-token="lpe_YOUR_EMBED_TOKEN"
  data-tenant-id="YOUR_TENANT_ID"
  data-experiment-id="homepage_pricing"
  data-auto-load
  data-container="#pricing-container"
></script>

Configuration

Configure the SDK with your API base URL, embed token, tenant ID, and experiment key. Optional settings control cookie behavior and debug logging.

The embed token is required: the SDK refuses to call the API without one. Create it on the Embed Snippet page and register every site the snippet runs on. It is publishable, so it belongs in page source; your secret API key does not. The API also refuses a secret key on a cross-origin call, because the preflight does not allow its header, but do not read that as a safety net: it says nothing about the key being readable by every visitor once it is in the page.

ParameterTypeRequiredDescription
apiBasestringYesYour Last Price API base URL
embedTokenstringYesPublishable embed token (lpe_...), valid only from the sites registered on it
tenantIdstringYesYour tenant identifier
experimentIdstringYesExperiment key to load pricing from, such as homepage_pricing. Not the UUID of the experiment record.
cookieNamestringNoCookie name for user ID persistence (default: lp_user_id)
cookieMaxAgenumberNoCookie expiry in seconds (default: 1 year)
debugbooleanNoEnable debug logging to console (default: false)
LastPrice.configure({
  apiBase: 'https://api.last-price.ai',
  embedToken: 'lpe_YOUR_EMBED_TOKEN',
  tenantId: 'YOUR_TENANT_ID',
  experimentId: 'homepage_pricing',
  cookieName: 'lp_user_id',      // optional
  cookieMaxAge: 31536000,          // optional (1 year)
  debug: false                     // optional
});

// Helper methods
const userId = LastPrice.getUserId();    // Get or create persistent user ID
const variant = LastPrice.getVariant();  // Get current variant assignment

Show Pricing

Render a pricing card into a container element. The SDK fetches the user's assigned variant and renders it automatically. Use a custom renderer for full control over the UI.

// Default renderer - pricing card with "Buy Now" button
LastPrice.showPricing('#pricing-container');

// With conversion callback. It REPLACES the default handler, so record the
// conversion yourself: the button records nothing otherwise.
LastPrice.showPricing('#pricing-container', {
  onConvert: async function(pricing) {
    await LastPrice.convert({ revenue: pricing.price });
    window.location.href = '/checkout';
  }
});

// Custom renderer - full control over markup
let convertPricing;

LastPrice.showPricing('#pricing-container', {
  customRenderer: function(pricing, convertFn) {
    convertPricing = convertFn;

    // The plan name and the features come from the variant's metadata and
    // end up in innerHTML, so escape them the way the default card does.
    var esc = function(value) {
      return String(value)
        .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
    };

    return '<div class="my-pricing">'
      + '<h2>' + esc(pricing.plan) + '</h2>'
      + '<p class="price">$' + esc(pricing.price) + '/mo</p>'
      + '<ul>' + (pricing.features || []).map(function(f) {
          return '<li>' + esc(f) + '</li>';
        }).join('') + '</ul>'
      // Deliberately NOT id="lp-convert-btn". The SDK wires that id up
      // itself, so a button carrying it and the handler below would both
      // fire on one click and record two conversions. Use the reserved id
      // and drop this listener, or use your own id and keep it.
      + '<button id="my-subscribe-btn" type="button">Subscribe</button>'
      + '</div>';
  }
});

document.addEventListener('click', function(event) {
  if (event.target && event.target.id === 'my-subscribe-btn' && convertPricing) {
    convertPricing();
  }
});

Track Conversions (SDK)

Record conversion events programmatically. Useful when the purchase flow happens outside the embedded pricing card (e.g., after a checkout process).

// Record a conversion with revenue
LastPrice.convert({
  revenue: 29.00,
  onSuccess: function(response) {
    console.log('Conversion recorded:', response);
  },
  onError: function(error) {
    console.error('Conversion failed:', error);
  }
});

// Simple conversion without revenue
LastPrice.convert();

Cost Tracking

Track costs across agents, customers, and operations. Record individual cost events and retrieve aggregated analytics to understand spending patterns over time.

Record Costs

Record a cost event associated with an agent, customer, or custom cost type.

ParameterTypeRequiredDescription
costTypestringYesCategory of cost (e.g., 'inference', 'compute', 'api_call')
amountnumberYesCost amount
currencystringYesISO 4217 currency code (e.g., USD)
agentIduuidNoAssociated agent identifier
customerIduuidNoAssociated customer identifier
metadataobjectNoAdditional context about the cost
# Record a cost
curl -X POST https://api.last-price.ai/api/costs \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "a1b2c3d4-e5f6-4789-8abc-1234567890ab",
    "customerId": "b2c3d4e5-f6a7-4890-bcde-2345678901bc",
    "costType": "inference",
    "amount": 0.0042,
    "currency": "USD",
    "metadata": {
      "model": "gpt-4",
      "tokens": 1250
    }
  }'

List Costs

Retrieve cost records with optional date-range filtering and pagination.

# List costs with date range
curl "https://api.last-price.ai/api/costs?startDate=2025-01-01T00:00:00Z&endDate=2025-01-31T23:59:59Z&limit=100&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{
  "data": [
    {
      "id": "d4e5f6a7-b8c9-4012-def0-456789012345",
      "agent_id": "a1b2c3d4-e5f6-4789-8abc-1234567890ab",
      "customer_id": "b2c3d4e5-f6a7-4890-bcde-2345678901bc",
      "cost_type": "inference",
      "amount": "0.0042",
      "currency": "USD",
      "metadata": { "model": "gpt-4", "tokens": 1250 },
      "created_at": "2025-01-15T14:30:00Z"
    }
  ],
  "pagination": {
    "limit": 100,
    "offset": 0,
    "total": 1
  }
}

Cost Analytics

Get aggregated cost analytics grouped by time period, agent, customer, or cost type.

ParameterTypeRequiredDescription
startDateISO 8601NoStart of the analysis window
endDateISO 8601NoEnd of the analysis window
groupByenumNoGroup results by: day, week, month, agent, customer, or type (default: day)
# Get cost analytics grouped by day
curl "https://api.last-price.ai/api/costs/analytics?startDate=2025-01-01&endDate=2025-01-31&groupBy=day" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response. Every count and sum is aggregated in Postgres and arrives as a
# decimal string, not a number.
{
  "analytics": [
    {
      "group_key": "2025-01-15T00:00:00.000Z",
      "count": "42",
      "total_amount": "1.7600",
      "avg_amount": "0.0420",
      "min_amount": "0.0010",
      "max_amount": "0.1500",
      "currency": "USD"
    }
  ],
  "totals": [
    {
      "total_count": "1250",
      "total_amount": "52.3000",
      "currency": "USD"
    }
  ],
  "groupBy": "day"
}

Demo Apps

Pre-built demo applications that showcase Last Price pricing experiments in action. Three demo apps are included: an e-commerce store, a marketplace, and a digital products platform - each with its own pricing experiment and variant configurations.

Overview

List all demo apps with their current status, variant counts, and performance metrics. Demo apps use a special demo tenant managed by the system.

# List demo apps
curl https://api.last-price.ai/api/demo-apps \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response
{
  "apps": [
    {
      "id": "ecommerce-store",
      "name": "E-Commerce Store",
      "description": "Product pricing experiment",
      "experimentKey": "ecommerce_pricing",
      "status": "active",
      "variants": 3,
      "views": 1250,
      "conversions": 89,
      "conversionRate": "7.1%"
    },
    {
      "id": "marketplace",
      "name": "Marketplace",
      "description": "Seller commission experiment",
      "experimentKey": "marketplace_commission",
      "status": "not_seeded",
      "variants": 2,
      "views": 0,
      "conversions": 0,
      "conversionRate": "0.0%"
    },
    {
      "id": "digital-products",
      "name": "Digital Products",
      "description": "Course pricing experiment",
      "experimentKey": "course_pricing",
      "status": "active",
      "variants": 3,
      "views": 620,
      "conversions": 41,
      "conversionRate": "6.6%"
    }
  ]
}

Seed Data

Initialize demo experiments by seeding sample data. You can seed all demo apps at once or a specific app by passing the app query parameter.

# Seed all demo apps
curl -X POST https://api.last-price.ai/api/demo-apps/seed \
  -H "Authorization: Bearer YOUR_TOKEN"

# Seed a specific demo app
curl -X POST "https://api.last-price.ai/api/demo-apps/seed?app=ecommerce-store" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response (201)
{
  "tenantId": "00000000-0000-0000-0000-000000000000",
  "results": [
    {
      "app": "ecommerce-store",
      "experimentId": "exp_uuid_...",
      "experimentKey": "ecommerce_pricing",
      "status": "active",
      "action": "created"
    }
  ]
}

A/B Testing with Elo

Elo is the A/B testing engine that powers deterministic variant assignment, experiment management, and conversion tracking.

How Variant Assignment Works

Elo uses SHA-256 hashing for deterministic variant assignment:

  • Same userId + experimentId → always the same variant
  • Assignment is deterministic, though the server may still read or persist assignment and view data
  • 100-bucket system supports percentage-based splits
  • Consistent across sessions, devices, and servers

Every request needs a credential: your secret API key from a server, or a publishable embed token from a page, which is what the snippet sends. Without one the API answers 401. The workspace comes from that credential, so tenantId is optional and only has to agree with it. The same user and experiment always return the same variant, using a 100-bucket SHA-256 hashing scheme.

ParameterTypeRequiredDescription
userIdstringYesUnique user/visitor identifier
tenantIdstringNoOptional. The workspace comes from your credential; when this is present it only has to agree with it. Query parameter for GET, body field for POST
# From a server. A page sends x-embed-token instead.
curl "https://api.last-price.ai/api/experiments/EXPERIMENT_KEY/pricing?userId=USER_ID" \
  -H "x-api-key: YOUR_API_KEY"

# Response
{
  "userId": "USER_ID",
  "experimentId": "EXPERIMENT_KEY",
  "variant": "control",
  "pricing": {
    "plan": "control",
    "price": 29,
    "features": ["Feature A", "Feature B"],
    "metadata": {}
  }
}

Create an Experiment

Create a new pricing experiment. Variant names must be "control" and "experiment"to match Elo's assignVariant() output. weight is optional - Elo defaults to an even 50/50 split when weights are not provided.

ParameterTypeRequiredDescription
namestringYesHuman-readable experiment name
keystringYesUnique key used in API URLs
variants[].namestringYesVariant name: must be "control" or "experiment", lowercase
variants[].pricenumberYesPrice for this variant
variants[].weightnumberNoRelative traffic weight, normalised against the other arm, so 70/30 and 0.7/0.3 are the same split. Set both or neither (default: even). The engine resolves traffic to 1%, so 1/99 is the most uneven split accepted
curl -X POST https://api.last-price.ai/api/tenants/YOUR_TENANT_ID/experiments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q2 Pricing Test",
    "key": "q2_pricing",
    "variants": [
      { "name": "control", "price": 29 },
      { "name": "experiment", "price": 39 }
    ]
  }'

View Experiment Results

ParameterTypeRequiredDescription
tenantIdstringYesTenant identifier (query parameter)
curl "https://api.last-price.ai/api/experiments/EXPERIMENT_KEY/results?tenantId=YOUR_TENANT_ID"

# Response
{
  "experimentId": "q2_pricing",
  "control": {
    "views": 5000,
    "conversions": 150,
    "revenue": "4350.00",
    "conversionRate": "3.00%",
    "arpu": "29.00"
  },
  "experiment": {
    "views": 5000,
    "conversions": 120,
    "revenue": "4680.00",
    "conversionRate": "2.40%",
    "arpu": "39.00"
  }
}

AI Pricing Optimization (Jale)

Jale is the AI pricing optimization engine - it provides elasticity analysis, revenue simulation, and intelligent price recommendations.

Price Recommendations

curl -X POST https://api.last-price.ai/api/compute/price \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: YOUR_TENANT_ID" \
  -d '{
    "function_name": "lastprice/jale-optimizer",
    "input": {
      "current_price": 49.99,
      "product_type": "saas_subscription",
      "market_data": {
        "competitor_prices": [29, 39, 59, 79],
        "current_conversion_rate": 0.025,
        "monthly_visitors": 10000,
        "churn_rate": 0.05
      }
    }
  }'

Elasticity Analysis

curl -X POST https://api.last-price.ai/api/compute/price \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: YOUR_TENANT_ID" \
  -d '{
    "function_name": "lastprice/jale-elasticity",
    "input": {
      "prices": [19, 29, 39, 49, 59],
      "quantities": [500, 350, 200, 120, 60],
      "product_category": "software"
    }
  }'

Psychological Pricing

curl -X POST https://api.last-price.ai/api/compute/price \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: YOUR_TENANT_ID" \
  -d '{
    "function_name": "lastprice/jale-psychological",
    "input": {
      "base_price": 50,
      "strategies": ["charm", "prestige", "anchor"]
    }
  }'

Competitive Price Scraping

The built-in scraper collects competitor pricing data to feed into Jale's optimization engine.

# Setup
cd scraper
pip install -r requirements.txt
from scrape_api import scrape_competitor_prices

# Scrape competitor pricing data
results = scrape_competitor_prices(
    urls=["https://competitor1.com/pricing", "https://competitor2.com/pricing"],
    selectors={"price": ".price-amount", "plan": ".plan-name"}
)

# Feed into Jale for optimization
import requests
requests.post("https://api.last-price.ai/api/compute/price", json={
    "function_name": "lastprice/jale-optimizer",
    "input": {
        "current_price": 49.99,
        "competitor_prices": [r["price"] for r in results]
    }
}, headers={"X-Tenant-ID": "YOUR_TENANT_ID"})

Commerce Control Plane

The commerce control plane turns computed prices into actually-shipped prices. It sits on top of the core PriceRouter and automates the full pipeline: pull catalog data from your stores → observe competitor pricing → match items → apply pricing policies → queue proposals for approval → push approved prices back via connector adapters.

The pipeline is implemented as seven packages orchestrated end-to-end:

StagePackagePurpose
Connectors@packages/connectorsPull catalog data from Shopify, Amazon SP-API, and memory adapters
Observations@packages/observationsIngest competitor prices from PriceAPI, Keepa, or manual POST
Matching@packages/matchingMatch catalog items to observed competitor items via Rosetta
Pricing Policy@packages/pricing-policyEvaluate floor/ceiling/competitor rules; calls PriceRouter internally
Approvals@packages/approvalsQueue proposals; auto-approve or hold for manual review
Execution Router@packages/execution-routerPush approved prices back to connectors with retry/backoff
Orchestration@packages/commerce-control-planeEnd-to-end runCycle(); exposes /api/commerce/* routes

Overview

Navigate to /commerce in the Oja dashboard to access the Commerce Control Plane UI. Tabs cover each pipeline stage. The Run cycle button at the top executes one full end-to-end pass (dry-run by default; set execute: true to actually push prices).

A scoped API key or token needs the scope for each step: commerce:catalog:read to read, commerce:configure to set up connectors, policies and data, commerce:proposal:draft to run a cycle, commerce:proposal:approve or commerce:proposal:reject to decide a proposal, and commerce:execute to push prices, including a cycle run with execute: true. A missing scope answers 403 insufficient_scope. Your dashboard session and unscoped keys have full access.

POST /api/commerce/cycle
{
  "execute": true   // set false for a dry-run (default)
}

// Response
{
  "counts": {
    "items":        42,
    "observations": 120,
    "matches":      38,
    "proposals":    38,
    "auto_approved": 12,
    "executed":     12,
    "failed":        0
  },
  "proposals":   38,
  "matches":     38,
  "executions": [
    { "proposal_id": "prop_...", "status": "executed", "attempts": 1, "last_response": {} },
    { "proposal_id": "prop_...", "status": "failed",   "attempts": 2, "reason": "connector_error" }
  ]
}

Connectors

Connector accounts link your storefronts to the pipeline. Supported platforms: shopify, amazon-sp, ucp-http, memory (testing).

# List connector accounts
GET /api/commerce/connectors

# Create a connector account
POST /api/commerce/connectors
{
  "platform": "shopify",
  "display_name": "My Shopify Store",
  "credentials": { "shop": "mystore.myshopify.com", "access_token": "..." },
  "metadata": {}
}

# Trigger a catalog sync
POST /api/commerce/connectors/{id}/sync

Catalog

Catalog items are pulled from connectors during a sync. Each item carries a current price, SKU, and optional GTIN used for competitor matching.

GET /api/commerce/catalog?limit=200

// Response
{
  "items": [
    {
      "id": "ci_...",
      "connector_account_id": "ca_...",
      "external_id": "shopify_prod_001",
      "title": "Blue Widget",
      "sku": "BLUE-WGT-001",
      "gtin": "00012345678905",
      "current_price": 29.99,
      "currency": "USD"
    }
  ]
}

Observations

Competitor observations are ingested automatically by the observation sources (PriceAPI, Keepa) during a cycle, or pushed manually via the API.

# Manually ingest a competitor observation
POST /api/commerce/observations
{
  "source": "manual",
  "competitor": "acme-widgets",
  "gtin": "00012345678905",
  "observed_price": 24.99,
  "currency": "USD"
}

Pricing Policies

Policies define the rules that produce price proposals. Each policy has an approval_mode: manual (human reviews all), auto (auto-approve when delta is within auto_approve_max_delta_pct), or shadow (log-only, never executes).

POST /api/commerce/policies
{
  "name": "Undercut competitors by 2%",
  "rules": [{ "id": "r1", "kind": "undercut_competitor", "by_pct": 2 }],
  "approval_mode": "auto",
  "auto_approve_max_delta_pct": 10,
  "is_active": true,
  "scope": {}
}

# Dry-run a policy against current catalog
POST /api/commerce/policies/{id}/evaluate

Approvals

Proposals generated by the pricing policy engine land in the approval queue with status proposed. Review and approve or reject them individually, or let auto-approval handle them.

GET /api/commerce/proposals?limit=200

POST /api/commerce/proposals/{id}/approve
{}

POST /api/commerce/proposals/{id}/reject
{ "reason": "price too aggressive" }

Execution

The execution router drains all approved proposals through the connector adapters, applying exponential backoff on transient failures. Terminal failures move the proposal to failed and capture the adapter response in the audit log.

POST /api/commerce/execution
// Drains all approved proposals

GET /api/commerce/audit?limit=200
// Append-only event log for the entire pipeline

Run Pipeline Cycle

A single POST /api/commerce/cycle runs every stage in order: pull catalogs → pull observations → match snapshots → decide prices → create proposals from decisions → execute approved. All stages are logged to the audit table. The dashboard Run cycle button calls this endpoint with execute: false (dry-run) by default.

POST /api/commerce/cycle
{ "execute": true }

// Cycle summary response
{
  "counts": {
    "items": 42, "observations": 120, "matches": 38,
    "proposals": 38, "auto_approved": 12, "executed": 12, "failed": 0
  },
  "proposals":  38,
  "matches":    38,
  "executions": [{ "proposal_id": "prop_...", "status": "executed", "attempts": 1 }]
}

Using the Oja Dashboard

The Oja dashboard is your control center for managing all aspects of the platform.

SectionWhat You Do There
DashboardOverview of active experiments, conversions, revenue
FunctionsRegister and manage pricing functions (built-in + custom)
ComputeRun single or batch pricing computations
Routing PoliciesConfigure how pricing requests are routed
CommerceCommerce control plane - connectors, catalog, policies, approvals, execution
Usage & CostsView token metering, cost breakdowns, billing periods
MarketplaceBrowse community pricing functions, publish your own
CustomersManage customer records
OrdersView and manage orders
InvoicesGenerate and track invoices
PaymentsTrack payment status
CreditsManage credit bundles
IntegrationsConfigure inference providers (OpenAI, Anthropic, etc.)
API KeysGenerate and manage API keys
SettingsCompany info, team management, billing, tax configuration

Workflow: Setting Up a Price Test

  1. Go to Functions → Verify your pricing functions are registered
  2. Go to Routing Policies → Set routing strategy
  3. Create an experiment via the API
  4. Embed the snippet or integrate via API on your site
  5. Go to Dashboard → Monitor impressions, conversions, and revenue
  6. Go to Usage & Costs → Track API usage and costs

Glossary

TermDefinition
TenantAn isolated account on Last Price (your store/company)
ExperimentAn A/B test comparing two or more price variants
VariantOne of the price options in an experiment (e.g., "$29" vs "$39")
AssignmentThe process of deterministically placing a user into a variant
ConversionA purchase or subscription triggered by a variant
PCNPricing Compute Network - the execution engine for pricing functions
EloThe A/B testing engine (named after the Elo rating system)
JaleThe AI pricing optimization engine
OjaThe admin dashboard for managing everything
FunctionA pricing computation unit (built-in or custom)
Routing PolicyRules for how pricing requests are distributed across functions
MeteringToken-based usage tracking for billing
Cost MultiplierA scaling factor on function execution cost (e.g., 5× for advanced AI)
SnippetThe embeddable JavaScript widget for any website
Fallback ChainOrdered backup functions used when the primary fails
Circuit BreakerAutomatic failure detection that stops routing to unhealthy functions
Price RouterMulti-vendor pricing facade - one API endpoint in front of all built-in and custom pricing models
RosettaSchema translation service - maps any price data format to canonical schemas using AI-assisted adapters
Canonical SchemaA standardized price data structure all platforms are mapped to (e.g., lastprice.price.v1)
Frozen AdapterAn immutable, versioned schema mapping promoted from a Rosetta plan
Commerce Control PlaneEnd-to-end pipeline: connectors → observations → matching → policies → approvals → execution
ConnectorAn integration adapter that pulls catalog data and pushes prices to a platform (Shopify, Amazon, etc.)
ObservationA competitor price data point ingested by the Commerce Control Plane
ProposalA candidate price change produced by a pricing policy, awaiting approval before execution
SignalsBehavioral event stream capturing pricing decisions, assignments, conversions, and agent actions