Faivri Docs
Docs / SDK

Get started

Build with Faivri

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.

Typed methodsBrowser + server

1. Install

Typed SDK — zero runtime deps, ships ESM + CJS + .d.ts.

npm install faivri

2. 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 → verdict
  • analyzeImage(file, opts)OCR an invoice photo
  • analyzeVoice(blob, opts)Transcribe + analyze
  • analyzePurchase(input)Used-vehicle deal check
  • getPurchaseAnalysis(id)Re-fetch a purchase verdict

Negotiate

  • negotiate(queryId)Target price + scripts
  • counterOffer(input)Respond to vendor pushback
  • feedback(input)Log final outcome

History

  • getHistory(page, limit)Your past verdicts
  • getVerdict(id) / deleteVerdict(id)Re-open or remove one
  • purgeHistory()Wipe all verdicts
  • getRecommendations(queryId)Next-step suggestions

Community

  • getCommunityPrices(domain)Shared pricing signals
  • getVendorScores(domain)Vendor fairness scores
  • getTrends(domain)Category trendlines

Vehicles

  • getVehicles()Saved vehicles on file
  • createVehicle(input)Add a vehicle to the garage
  • getMaintenanceSchedule(id)Upcoming service intervals

Account

  • getUsage()Plan + monthly remaining
  • getPlans()Public plan catalog
  • getSavings() / getSavingsProfile()Lifetime savings

Misc

  • getProviders()LLM provider matrix
  • joinWaitlist(email, source?)Add email to waitlist