SDK guide
TypeScript client
A single typed client for the full Faivri surface — analyze, negotiate, history, community intel, quota, and billing. Works the same in the browser and on the server; swap between a Clerk JWT and a server API key.
1. Install
Typed SDK — zero runtime deps, ships ESM + CJS + .d.ts.
npm install faivri2. Create the client
Wire once, call anywhere. The accessToken getter is awaited on every request, so rotating Clerk JWTs just work.
// Browser (Next.js client component)
import { createFaivriClient } from 'faivri'
const faivri = createFaivriClient({
baseUrl: process.env.NEXT_PUBLIC_API_URL,
accessToken: async () => await clerk.session?.getToken() ?? null,
})3. Analyze → negotiate → feedback
The core flow. Every verdict carries a stable id you pass into the negotiate call to get a target price and ready-to-send scripts.
const verdict = await faivri.analyze({
query: 'Front brake pads + rotor quote',
domain: 'auto',
quoted_price: 740,
city: 'Los Angeles',
country: 'US',
})
if (verdict.expected_overpay > 0) {
const scripts = await faivri.negotiate(verdict.id)
console.log(scripts.target_price, scripts.scripts)
}
// Follow up after the conversation
await faivri.feedback({
query_id: verdict.id,
final_price: 540,
outcome: 'accepted',
})4. Check quota before you render the UI
The backend enforces plan limits server-side. Call getUsage() on page mount to disable the submit button before the user hits 402.
// Before enabling the analyze button, check remaining quota
const { plan, limit, remaining, reset_at, unlimited } = await faivri.getUsage()
if (!unlimited && remaining === 0) {
// Show the upgrade modal — quota resets on ${reset_at}
}
// For rendering the pricing page
const catalog = await faivri.getPlans()
// [{ key: 'scout', monthly_limit: 3, unlimited: false }, ...]5. Typed error handling
Every non-2xx throws a FaivriError with status and body preserved. Branch on 402 for quota, 429 for rate-limit, render the server message otherwise.
import { FaivriError } from 'faivri'
try {
await faivri.analyze({ query: 'MRI cost' })
} catch (err) {
if (err instanceof FaivriError) {
if (err.status === 402) {
// Quota exhausted; err.body.plan + err.body.reset_at are populated
showUpgradeModal(err.body)
} else if (err.status === 429) {
// Rate limited (IP cap or per-minute)
showRetryToast()
} else {
showError(err.message)
}
}
}6. Server-side usage
Skip Clerk, pass an API key instead. Useful for cron jobs, webhooks, and edge functions that reconcile verdicts with your own data.
// Server / edge function — no Clerk, use a server key
import { FaivriClient } from 'faivri'
const client = new FaivriClient({
baseUrl: process.env.FAIVRI_API_URL,
apiKey: process.env.FAIVRI_API_KEY,
})
const [history, savings, usage] = await Promise.all([
client.getHistory(1, 10),
client.getSavingsProfile(),
client.getUsage(),
])Method reference
Every endpoint on the Faivri REST API, typed and callable.
Analyze
analyze(input)Text quote → verdictanalyzeImage(file, opts)OCR an invoice photoanalyzeVoice(blob, opts)Transcribe + analyzeanalyzePurchase(input)Used-vehicle deal checkgetPurchaseAnalysis(id)Re-fetch a purchase verdict
Negotiate
negotiate(queryId)Target price + scriptscounterOffer(input)Respond to vendor pushbackfeedback(input)Log final outcome
History
getHistory(page, limit)Your past verdictsgetVerdict(id) / deleteVerdict(id)Re-open or remove onepurgeHistory()Wipe all verdictsgetRecommendations(queryId)Next-step suggestions
Community
getCommunityPrices(domain)Shared pricing signalsgetVendorScores(domain)Vendor fairness scoresgetTrends(domain)Category trendlines
Vehicles
getVehicles()Saved vehicles on filecreateVehicle(input)Add a vehicle to the garagegetMaintenanceSchedule(id)Upcoming service intervals
Account
getUsage()Plan + monthly remaininggetPlans()Public plan cataloggetSavings() / getSavingsProfile()Lifetime savings
Misc
getProviders()LLM provider matrixjoinWaitlist(email, source?)Add email to waitlist