Skip to main content

What You’ll Build

This tutorial walks you through building a Next.js 14+ (App Router) full-stack application using a dual-client architecture:
  • Profy client — initialized with your platform API key (sk-pro-...). Used to call AI experts and chat completions. The cost is charged to your platform account.
  • ProfyApp client — initialized with your OAuth App credentials. Used to bill end users for usage via reportEvent, using each user’s OAuth token.
This separation keeps AI invocation simple (one API key, no per-user tokens) while still enabling per-user billing through OAuth.
The Profy client (API key) and ProfyApp client (OAuth) serve different purposes. You use the API key to call AI — the platform charges your account. You use OAuth tokens to bill your end users for the actions they take in your app.

Prerequisites

Profy Developer Account

Register a Profy account, then create an App in the developer dashboard to get your Client ID, Client Secret, and API Key (sk-pro-...)

Development Environment

Node.js 18+, npm/pnpm/bun, basic Next.js and React experience

Step 1: Create a Next.js Project and Install Dependencies

Step 2: Configure Environment Variables

.env.local
Never commit .env.local to version control. Make sure .gitignore includes this file. The PROFY_API_KEY and PROFY_APP_SECRET are sensitive credentials.

Step 3: Initialize Both SDK Clients

Create a shared module that exports both the Profy client (for AI calls) and the ProfyApp client (for OAuth and billing):
lib/profy.ts
Both clients should be used as singletons on the server side. Never expose apiKey or clientSecret to the browser.

Step 4: Create the OAuth Login Page

Use profyApp.authorizationUrl() to generate the OAuth URL. The events:write scope is required for billing event reporting.
app/page.tsx

Step 5: Handle the OAuth Callback

When the user authorizes your app, Profy redirects back with a code parameter. Exchange it for an OAuthToken and persist it.
app/api/callback/route.ts
The OAuthToken returned by exchangeCode contains accessToken, refreshToken, expiresAt, tokenType, and scope. Always persist the full token object so you can refresh it later.

Step 6: Token Persistence

You need to store OAuth tokens securely and refresh them when they expire. Here we show a manual DB store pattern using the SDK’s isTokenExpired() and app.refresh() helpers.
The example below uses an in-memory Map for simplicity. In production, replace it with your real database (PostgreSQL, MySQL, Redis, etc.). In-memory storage will lose all tokens when the server restarts.
lib/token-store.ts
isTokenExpired(token, leewayMs?) checks whether the token’s expiresAt timestamp has passed (with an optional leeway in milliseconds). A 60-second leeway prevents edge cases where the token expires between the check and the API call.
Here’s an example of what a production database store might look like using raw SQL:
lib/token-store-production-example.ts

Step 7: Report Billing Events

Use profyApp.reportEvent() with the user’s OAuth token to bill them. The onRefresh callback handles automatic token rotation during the API call.
app/api/report-event/route.ts
The onRefresh callback on reportEvent is called if the SDK internally refreshes the token during the API call. This ensures your stored token stays up to date without a separate refresh step.
Client-side usage example:
app/dashboard/page.tsx

Step 8: Call AI (Expert or Chat)

Use the Profy client (API key) for all AI calls. This is completely separate from the OAuth billing flow — the AI cost is charged to your platform account.

Call an Expert

app/api/ai/expert/route.ts

Call an Expert (Streaming)

app/api/ai/expert-stream/route.ts

Chat Completions

app/api/ai/chat/route.ts

Combining AI Calls with Billing

In a real application, you typically call the AI and report a billing event together. Here’s how:
lib/ai-with-billing.ts
You can decouple AI calls from billing. For example, you might call the AI first and only report the billing event after confirming the response was successful. Use the idempotencyKey to prevent duplicate charges if you need to retry.

Step 9: Error Handling

The SDK throws typed errors that you can catch precisely. All error classes extend ProfyApiError.
lib/error-handler.ts
Unified handling in the API route:
app/api/report-event/route.ts

Complete Project Structure

FAQ

Next Steps

Per-Use Billing SaaS

PER_USE mode: fixed-price per-use billing

AI Metered Billing

METERED mode: billing by token consumption for AI model calls

Webhook Event Handling

Receive real-time user event notifications from the Profy platform

Token Management Best Practices

Multi-user management, concurrent refresh, secure storage