# A/B Testing Source: https://docs.withperf.pro/advanced/ab-testing Run A/B tests to compare model performance # A/B Testing **Coming Soon** This feature is currently in development. You'll be able to: * Compare multiple models side-by-side * Run controlled experiments on production traffic * Measure quality differences statistically * Analyze cost vs. quality tradeoffs * Make data-driven model selection decisions For early access or custom requirements, contact [sales@withperf.pro](mailto:sales@withperf.pro). # Custom Orchestration Rules Source: https://docs.withperf.pro/advanced/custom-routing Configure custom orchestration rules for your workloads # Custom Orchestration Rules **Coming Soon** This feature is currently in development. You'll be able to: * Define custom orchestration logic * Set provider preferences per endpoint * Configure model fallback chains * Create task-specific routing rules * Implement A/B testing strategies For early access or custom requirements, contact [sales@withperf.pro](mailto:sales@withperf.pro). # Enterprise Features Source: https://docs.withperf.pro/advanced/enterprise Advanced features for enterprise customers # Enterprise Features **Coming Soon** Enterprise-grade features currently in development: ## Security & Compliance * Single Sign-On (SSO) integration * Custom data residency requirements * Advanced audit logging * SOC 2 Type II compliance reporting ## Deployment Options * Private cloud deployment * On-premise installation * Air-gapped environments * Dedicated infrastructure ## Advanced Capabilities * Bring Your Own Model (BYOM) * Custom model fine-tuning * Private model hosting * SLA guarantees with penalties ## Support & Services * 24/7 dedicated support * Custom integration assistance * Performance optimization consulting * Training and onboarding For enterprise inquiries, contact [sales@withperf.pro](mailto:sales@withperf.pro). # Fine-tuning Preferences Source: https://docs.withperf.pro/advanced/preferences Customize Perf orchestration for your specific needs # Fine-tuning Preferences **Coming Soon** This feature is currently in development. You'll be able to: * Configure model preferences per task type * Set quality vs. cost tradeoff parameters * Define custom complexity scoring * Train preferences from user feedback * Export and import preference profiles For early access or custom requirements, contact [sales@withperf.pro](mailto:sales@withperf.pro). # Audio API Source: https://docs.withperf.pro/api-reference/audio Text-to-speech (TTS) and speech-to-text (STT) transcription # Audio API Reference Generate natural-sounding speech from text (TTS) and transcribe audio to text (STT) using OpenAI's Whisper model. Both endpoints follow the OpenAI Audio API format. ## Text-to-Speech (TTS) Convert text into lifelike spoken audio. ### Endpoint ``` POST https://api.withperf.pro/v1/audio/speech ``` ### Request Body | Parameter | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ----------------------------------- | | `model` | string | Yes | - | TTS model (`tts-1` or `tts-1-hd`) | | `input` | string | Yes | - | Text to synthesize (max 4096 chars) | | `voice` | string | Yes | - | Voice to use | | `response_format` | string | No | `mp3` | Audio format | | `speed` | number | No | `1.0` | Speaking speed (0.25 to 4.0) | ### Supported Voices | Voice | Description | Best For | | --------- | -------------------- | ---------------------- | | `alloy` | Neutral, balanced | General purpose | | `echo` | Warm, conversational | Podcasts, narration | | `fable` | Expressive, dramatic | Storytelling | | `onyx` | Deep, authoritative | Professional content | | `nova` | Friendly, upbeat | Marketing, tutorials | | `shimmer` | Clear, gentle | Audiobooks, meditation | ### Supported Formats | Format | MIME Type | Description | | ------ | ---------- | ----------------------------- | | `mp3` | audio/mpeg | Compressed, widely compatible | | `opus` | audio/opus | Optimized for streaming | | `aac` | audio/aac | Good for mobile | | `flac` | audio/flac | Lossless quality | | `wav` | audio/wav | Uncompressed | | `pcm` | audio/pcm | Raw audio | ### Models | Model | Quality | Latency | Price | | ---------- | --------------- | ------- | ---------------- | | `tts-1` | Standard | Fast | \$0.015/1K chars | | `tts-1-hd` | High Definition | Slower | \$0.030/1K chars | ### Request Examples #### cURL ```bash theme={null} curl -X POST https://api.withperf.pro/v1/audio/speech \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "model": "tts-1", "input": "Welcome to Perf AI. We make AI routing simple and cost-effective.", "voice": "nova", "response_format": "mp3", "speed": 1.0 }' \ --output speech.mp3 ``` #### JavaScript ```javascript theme={null} const response = await fetch('https://api.withperf.pro/v1/audio/speech', { method: 'POST', headers: { 'Authorization': 'Bearer pk_live_abc123', 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'tts-1', input: 'Welcome to Perf AI. We make AI routing simple and cost-effective.', voice: 'nova' }) }); const audioBlob = await response.blob(); const audioUrl = URL.createObjectURL(audioBlob); // Play or download the audio ``` #### Python ```python theme={null} import requests response = requests.post( 'https://api.withperf.pro/v1/audio/speech', headers={ 'Authorization': 'Bearer pk_live_abc123', 'Content-Type': 'application/json' }, json={ 'model': 'tts-1', 'input': 'Welcome to Perf AI. We make AI routing simple and cost-effective.', 'voice': 'nova' } ) with open('speech.mp3', 'wb') as f: f.write(response.content) ``` ### Response Returns binary audio data with these headers: | Header | Description | | ------------------- | ------------------------------------ | | `Content-Type` | Audio MIME type (e.g., `audio/mpeg`) | | `X-Perf-Request-Id` | Request tracking ID | | `X-Perf-Model-Used` | Model that generated the audio | | `X-Perf-Cost-Usd` | Generation cost | | `X-Perf-Latency-Ms` | Generation latency | *** ## Speech-to-Text (Transcription) Transcribe audio files into text using OpenAI's Whisper model. ### Endpoint ``` POST https://api.withperf.pro/v1/audio/transcriptions ``` ### Request Body (multipart/form-data) | Parameter | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------ | | `file` | file | Yes | - | Audio file to transcribe | | `model` | string | Yes | - | Model (`whisper-1`) | | `language` | string | No | auto | ISO-639-1 language code | | `response_format` | string | No | `json` | Output format | ### Supported Audio Formats * MP3 (`.mp3`) * MP4 (`.mp4`, `.m4a`) * MPEG (`.mpeg`, `.mpga`) * WAV (`.wav`) * WebM (`.webm`) * OGG (`.ogg`) * FLAC (`.flac`) **Max file size:** 25 MB ### Response Formats | Format | Description | | -------------- | ----------------------------- | | `json` | Simple JSON with `text` field | | `text` | Plain text only | | `srt` | SubRip subtitle format | | `vtt` | WebVTT subtitle format | | `verbose_json` | Detailed JSON with timestamps | ### Pricing | Model | Price | | ----------- | ----------------------- | | `whisper-1` | \$0.006/minute of audio | ### Request Examples #### cURL ```bash theme={null} curl -X POST https://api.withperf.pro/v1/audio/transcriptions \ -H "Authorization: Bearer pk_live_abc123" \ -F "file=@meeting.mp3" \ -F "model=whisper-1" \ -F "language=en" \ -F "response_format=json" ``` #### JavaScript ```javascript theme={null} const formData = new FormData(); formData.append('file', audioFile); formData.append('model', 'whisper-1'); formData.append('language', 'en'); const response = await fetch('https://api.withperf.pro/v1/audio/transcriptions', { method: 'POST', headers: { 'Authorization': 'Bearer pk_live_abc123' }, body: formData }); const data = await response.json(); console.log(data.text); ``` #### Python ```python theme={null} import requests with open('meeting.mp3', 'rb') as audio_file: response = requests.post( 'https://api.withperf.pro/v1/audio/transcriptions', headers={ 'Authorization': 'Bearer pk_live_abc123' }, files={ 'file': ('meeting.mp3', audio_file, 'audio/mpeg') }, data={ 'model': 'whisper-1', 'language': 'en' } ) data = response.json() print(data['text']) ``` ### Response (JSON format) ```json theme={null} { "text": "Welcome to the weekly team meeting. Today we'll discuss our Q1 goals and the upcoming product launch.", "perf": { "request_id": "req_trans_abc123", "model_used": "whisper-1", "audio_duration_seconds": 12.5, "cost_usd": 0.00125, "latency_ms": 2341 } } ``` ### Response (verbose\_json format) ```json theme={null} { "text": "Welcome to the weekly team meeting.", "segments": [ { "id": 0, "start": 0.0, "end": 2.5, "text": "Welcome to the weekly team meeting." } ], "language": "en", "perf": { "request_id": "req_trans_abc123", "model_used": "whisper-1", "audio_duration_seconds": 2.5, "cost_usd": 0.00025, "latency_ms": 890 } } ``` ## Error Responses ### 400 Bad Request ```json theme={null} { "error": { "type": "invalid_request", "message": "input text is required", "param": "input" } } ``` ### 400 File Too Large ```json theme={null} { "error": { "type": "invalid_request", "message": "File size exceeds maximum of 25MB" } } ``` ### 400 Unsupported Format ```json theme={null} { "error": { "type": "invalid_request", "message": "Unsupported audio format. Supported: mp3, mp4, mpeg, mpga, m4a, wav, webm" } } ``` ## Rate Limits | Tier | TTS Requests/Min | Transcription Min/Day | | ---------- | ---------------- | --------------------- | | Free | 10 | 10 minutes | | Pro | 60 | 500 minutes | | Enterprise | Custom | Custom | ## Related Endpoints * [Chat API](./chat) - Text generation with audio input support * [Image Generation](./images) - Generate images * [Video Generation](./video) - Generate video content # Chat API Source: https://docs.withperf.pro/api-reference/chat Complete Chat API reference documentation # Chat API Reference The Chat API is Perf's primary endpoint for text generation. It automatically routes your request to the optimal model based on task type, complexity, and your cost constraints. The response format is **OpenAI-compatible**, making it easy to integrate with existing applications. ## Endpoint ``` POST https://api.withperf.pro/v1/chat ``` ## Authentication Include your API key in the Authorization header: ``` Authorization: Bearer YOUR_API_KEY ``` ## Request Body ### Required Parameters | Parameter | Type | Description | | ---------- | ----- | -------------------------------------------------- | | `messages` | array | Array of message objects with `role` and `content` | ### Optional Parameters | Parameter | Type | Default | Description | | ------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------- | | `max_cost_per_call` | number | none | Maximum cost in USD for this request. If exceeded, Perf will try to use a cheaper model. | | `document_id` | string | none | ID of an uploaded document to use as context. The document content is automatically injected into the request. | | `document_ids` | string\[] | none | Array of document IDs to use as context. Use when referencing multiple documents in a single request. | | `schema` | object | none | Inline JSON Schema to validate and enforce on the response. See [Schema Enforcement](./schemas). | | `schema_id` | string | none | ID or slug of a saved schema. See [Schema Enforcement](./schemas). | | `schema_strict` | boolean | false | Disable auto-repair, fail on any schema mismatch. | ### Message Object ```json theme={null} { "role": "user" | "assistant" | "system", "content": "string" | ContentPart[] } ``` ### Multimodal Content (Vision) The `content` field can be a string for text-only messages, or an array of content parts for multimodal messages (images, audio, video, documents). #### Content Part Types | Type | Format | Description | | ------------- | -------------------------------------------------------------------------------------- | ------------------------------------ | | `text` | `{ type: "text", text: "..." }` | Text content | | `image_url` | `{ type: "image_url", image_url: { url: "...", detail?: "low" \| "high" \| "auto" } }` | Image (base64 data URL or HTTPS URL) | | `input_audio` | `{ type: "input_audio", input_audio: { data: "...", format: "wav" \| "mp3" } }` | Base64-encoded audio | | `video_url` | `{ type: "video_url", video_url: { url: "..." } }` | Video URL | | `document` | `{ type: "document", document: { type: "pdf", data: "...", name?: "..." } }` | Base64-encoded document | #### Vision Request Example ```json theme={null} { "messages": [ { "role": "user", "content": [ { "type": "text", "text": "What's in this image?" }, { "type": "image_url", "image_url": { "url": "https://example.com/image.jpg", "detail": "high" } } ] } ] } ``` #### Base64 Image Example ```json theme={null} { "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this screenshot" }, { "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." } } ] } ] } ``` Perf automatically routes vision requests to models with image understanding capabilities (GPT-4o, Claude 3.5 Sonnet, Gemini Pro Vision). ## Request Example ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_test_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "system", "content": "You are a helpful assistant that extracts structured data." }, { "role": "user", "content": "Extract name, email, and phone from: John Doe, contact at john@example.com or call 555-1234" } ], "max_cost_per_call": 0.005 }' ``` ## Response ### Success Response (200 OK) The response follows the **OpenAI Chat Completion format**: ```json theme={null} { "id": "chatcmpl-abc123xyz", "object": "chat.completion", "created": 1705312200, "model": "gpt-4o-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "{\n \"name\": \"John Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-1234\"\n}" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 47, "completion_tokens": 28, "total_tokens": 75 }, "perf": { "task_type": "extraction", "complexity": 0.3, "model_selected": "gpt-4o-mini", "latency_ms": 342, "fallback_used": false, "validation_passed": true } } ``` ### Response Fields | Field | Type | Description | | --------------------------- | ------- | ------------------------------------------------------------------ | | `id` | string | Unique identifier for the completion | | `object` | string | Always `"chat.completion"` | | `created` | number | Unix timestamp of when the completion was created | | `model` | string | The model that processed your request | | `choices` | array | Array of completion choices | | `choices[].message.content` | string | The generated text response | | `choices[].finish_reason` | string | Why the model stopped (`"stop"`, `"length"`, etc.) | | `usage.prompt_tokens` | number | Input tokens consumed | | `usage.completion_tokens` | number | Output tokens generated | | `usage.total_tokens` | number | Total tokens used | | `perf.task_type` | string | Detected task type | | `perf.complexity` | number | Estimated complexity (0-1) | | `perf.model_selected` | string | Model chosen by router | | `perf.latency_ms` | number | Response time in milliseconds | | `perf.fallback_used` | boolean | Whether fallback model was used | | `perf.validation_passed` | boolean | Whether output passed quality checks | | `perf.document_id` | string | ID of the document used as context (if `document_id` was provided) | | `perf.policy_evaluation` | object | Policy evaluation results (if policies configured) | | `perf.content_evaluation` | object | Content evaluation results (if content policies configured) | ### Response Headers Perf includes additional metadata in response headers: | Header | Description | | ------------------- | ------------------------------------ | | `X-Perf-Model` | The model that processed the request | | `X-Perf-Fallback` | `true` if a fallback model was used | | `X-Perf-Latency-Ms` | Response time in milliseconds | ### Policy Evaluation (Pro+) When routing policies are configured for your project, the response includes policy evaluation details: ```json theme={null} { "perf": { "policy_evaluation": { "result": "allow", "violations": [], "modifications": [ { "type": "model_override", "from": "gpt-4o", "to": "gemini-2.5-flash", "reason": "Budget Mode policy - prefer cheaper models" } ], "policies_evaluated": ["policy_abc123"], "policies_matched": ["policy_abc123"] } } } ``` | Field | Type | Description | | -------------------- | ------ | ----------------------------------------------------------- | | `result` | string | Overall result: `allow`, `warn`, `soft_block`, `hard_block` | | `violations` | array | List of policy violations (if any) | | `modifications` | array | Changes made by policies (model overrides, etc.) | | `policies_evaluated` | array | IDs of policies that were checked | | `policies_matched` | array | IDs of policies that triggered | **Policy Results:** * `allow` - Request proceeds normally * `warn` - Request proceeds with warning logged * `soft_block` - Request proceeds with modifications applied * `hard_block` - Request rejected with 403 error See [Policies API](./policies) for available policy templates and configuration. ### Content Evaluation (Pro+) When content policies are configured (PII detection, term filtering), the response includes content evaluation details: ```json theme={null} { "perf": { "content_evaluation": { "result": "allow", "phase": "post_response", "pii_detected": true, "pii_count": 2, "redacted": true, "criteria_passed": 0, "criteria_failed": 0, "latency_ms": 15 } } } ``` | Field | Type | Description | | ----------------- | ------- | -------------------------------------------------- | | `result` | string | Overall result: `allow`, `warn`, `redact`, `block` | | `phase` | string | Evaluation phase: `post_response` | | `pii_detected` | boolean | Whether PII was detected in output | | `pii_count` | integer | Number of PII items detected | | `redacted` | boolean | Whether content was redacted | | `criteria_passed` | integer | Number of criteria passed (if using LLM-as-judge) | | `criteria_failed` | integer | Number of criteria failed | | `latency_ms` | integer | Content evaluation latency | **Content Results:** * `allow` - Content passes all checks * `warn` - Content flagged but returned * `redact` - PII/terms redacted from output (e.g., `john@example.com` → `[REDACTED]`) * `block` - Content blocked, error returned **Supported PII Types:** * `ssn` - Social Security Numbers * `credit_card` - Credit card numbers (with Luhn validation) * `email` - Email addresses * `phone_us` - US phone numbers * `ip_address` - IP addresses * `date_of_birth` - Dates of birth ## Task Types Perf automatically detects your task type for optimal routing: | Task Type | Description | Example | | ---------------- | -------------------------------- | --------------------------------- | | `extraction` | Extracting structured data | "Extract email from text" | | `classification` | Categorizing or labeling | "Classify sentiment" | | `summarization` | Condensing information | "Summarize this article" | | `reasoning` | Logic and analysis | "Solve this math problem" | | `code` | Code generation/explanation | "Write a binary search" | | `writing` | Creative or professional writing | "Write a blog post" | | `vision` | Image understanding | Requests with image content parts | | `audio` | Audio understanding | Requests with audio content parts | ## Generation Intent Detection The Chat API intelligently detects when your prompt is requesting media generation (images, video, audio) and automatically routes to the appropriate generation model. ### Example ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Generate an image of a sunset over mountains" } ] }' ``` Perf understands this is an image generation request and routes to DALL-E, returning the generated image URL in the response. For more control over generation parameters (model selection, dimensions, quality), use the dedicated generation endpoints: * [Image Generation API](./images) - DALL-E, Stable Diffusion, Flux, and more * [Video Generation API](./video) - Veo, Runway, Luma, Pika * [Audio API](./audio) - Text-to-speech and transcription ## Document Context Reference uploaded documents directly in your chat requests. Perf automatically retrieves the document content and injects it as context for the AI model. This is ideal for extracting structured data from PDFs, answering questions about uploaded files, or any task that requires grounding the AI response in specific document content. ### How It Works 1. Upload a document via `POST /v1/documents` (see [Documents API](./tools#documents--rag)) 2. Wait for the document status to become `ready` 3. Pass the `document_id` in your chat request 4. Perf retrieves the document content and includes it as context For small documents (under \~40 pages), the full content is injected. For larger documents, Perf uses semantic search (RAG) to find and inject the most relevant sections based on your message. ### Single Document ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Extract all technical specifications from this datasheet" } ], "document_id": "doc_abc123" }' ``` ### Multiple Documents ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Compare the warranty terms across these contracts" } ], "document_ids": ["doc_abc123", "doc_def456"] }' ``` ### Document + Schema (Structured Extraction) Combine `document_id` with `schema_id` to extract structured data from documents. Upload a PDF, define a schema, and get validated JSON back. ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Extract all specifications from this solar panel datasheet" } ], "document_id": "doc_abc123", "schema_id": "solar-panel-specs" }' ``` Response with structured, schema-validated output: ```json theme={null} { "choices": [{ "message": { "content": "{\"manufacturer\": \"Vikram Solar\", \"model\": \"Somera Grand 580\", \"max_power_w\": 580, \"efficiency_pct\": 22.53, \"voltage_mpp_v\": 44.65, \"weight_kg\": 28.5, \"dimensions_mm\": \"2278x1134x30\", \"warranty_years\": 30}" } }], "perf": { "document_id": "doc_abc123", "schema_validation": { "enabled": true, "passed": true } } } ``` ### Document Error Responses | Status | Condition | Description | | ------ | ------------------- | ------------------------------------------------------------------- | | 400 | Document not found | The `document_id` does not exist or does not belong to your project | | 400 | Document failed | The document failed processing and cannot be used | | 409 | Document processing | The document is still being processed. Retry after a few seconds. | ## Cost Control ### Budget Enforcement When you set `max_cost_per_call`, Perf will: 1. Estimate the cost for the optimal model 2. If estimated cost > budget, select a cheaper alternative 3. Process with the selected model 4. Include a `cost_warning` in the `perf` object if budget was a factor ```json theme={null} { "messages": [...], "max_cost_per_call": 0.001 } ``` ## Quality Validation Perf automatically validates outputs and retries if needed: ### Validation Checks * JSON format correctness (for extraction/classification tasks) * Refusal detection ("I cannot assist with that...") * Incomplete response detection ### Retry Logic If validation fails: 1. Retry with the same model (max 1 retry) 2. If still failing, escalate to fallback model 3. Return best available result ## Multi-Turn Conversations Include conversation history in the `messages` array: ```json theme={null} { "messages": [ {"role": "user", "content": "What is photosynthesis?"}, {"role": "assistant", "content": "Photosynthesis is the process..."}, {"role": "user", "content": "How does it differ from cellular respiration?"} ] } ``` Perf automatically: * Summarizes long conversation history to fit context windows * Maintains semantic coherence * Optimizes for cost by compressing older messages ## Error Responses ### 400 Bad Request ```json theme={null} { "error": { "type": "invalid_request", "message": "messages array is required", "param": "messages" } } ``` ### 401 Unauthorized ```json theme={null} { "error": { "type": "authentication_error", "message": "Invalid API key" } } ``` ### 429 Too Many Requests ```json theme={null} { "error": { "type": "rate_limit_exceeded", "message": "Rate limit exceeded", "retry_after": 30 } } ``` Response headers: ``` X-RateLimit-Limit: 60 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1673456789 Retry-After: 30 ``` ### 500 Internal Server Error ```json theme={null} { "error": { "type": "server_error", "message": "An internal error occurred", "request_id": "req_abc123" } } ``` ### 503 Service Unavailable ```json theme={null} { "error": { "type": "service_unavailable", "message": "All providers are currently experiencing issues", "retry_after": 60 } } ``` ## Structured Output For extraction and classification tasks, Perf automatically detects when JSON output is needed and routes to models that excel at structured output. To get JSON output, simply ask for it in your prompt: ```json theme={null} { "messages": [ { "role": "user", "content": "List 3 benefits of exercise. Return as JSON with fields: benefit, description, category" } ] } ``` Perf will detect this is an extraction task and route accordingly. ## Rate Limits | Tier | Requests/Minute | Requests/Day | | ---------- | --------------- | ------------ | | Free | 60 | 1,000 | | Pro | 300 | 100,000 | | Enterprise | Custom | Custom | ## Best Practices ### 1. Set Appropriate Budgets ```json theme={null} { "max_cost_per_call": 0.001 // Simple extraction } ``` ```json theme={null} { "max_cost_per_call": 0.05 // Complex reasoning } ``` ### 2. Use System Messages Guide model behavior with system messages: ```json theme={null} { "messages": [ { "role": "system", "content": "You are a concise assistant. Keep responses under 50 words." }, { "role": "user", "content": "Explain gravity" } ] } ``` ### 3. Optimize for Task Type Be explicit about the task for better routing: ```json theme={null} { "messages": [ { "role": "user", "content": "EXTRACT the following data as JSON: name, age, location from: 'Sarah is 28 and lives in Seattle'" } ], "response_format": "json" } ``` ### 4. Handle Errors Gracefully ```python theme={null} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() except requests.exceptions.HTTPError as e: if response.status_code == 429: # Implement exponential backoff retry_after = int(response.headers.get('Retry-After', 60)) time.sleep(retry_after) elif response.status_code >= 500: # Provider issue, retry with different request pass ``` ## SDK Support Official SDKs coming soon: * Python SDK * Node.js SDK * Go SDK * Ruby SDK ## Related Endpoints * [Schema Enforcement](./schemas) - Validate and auto-repair LLM outputs * [Tools API](./tools) - Documents/RAG, web search, and memory * [Streaming API](./streaming) - For real-time responses * [Metrics API](./metrics) - For analytics and monitoring * [Logs API](./logs) - For debugging and audit trails ## Support * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) * **Email**: [support@withperf.pro](mailto:support@withperf.pro) * **Status**: [status.withperf.pro](https://status.withperf.pro) # Image Generation Source: https://docs.withperf.pro/api-reference/images Generate images with DALL-E, Stable Diffusion, Flux, and more # Image Generation API Generate images using state-of-the-art models including DALL-E 3, Stable Diffusion 3, Flux, and Ideogram. The API follows the OpenAI Images API format for easy integration. ## Endpoint ``` POST https://api.withperf.pro/v1/images/generations ``` ## Authentication Include your API key in the Authorization header: ``` Authorization: Bearer YOUR_API_KEY ``` ## Request Body ### Required Parameters | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------------- | | `prompt` | string | A text description of the desired image (max 4000 chars) | ### Optional Parameters | Parameter | Type | Default | Description | | ----------------- | ------ | ----------- | -------------------------------------------------------------- | | `model` | string | `dall-e-3` | Model to use for generation | | `n` | number | `1` | Number of images to generate (1 for DALL-E 3, 1-10 for others) | | `size` | string | `1024x1024` | Image dimensions | | `quality` | string | `standard` | Quality level (`standard` or `hd`, DALL-E 3 only) | | `style` | string | `vivid` | Style preset (`vivid` or `natural`, DALL-E 3 only) | | `response_format` | string | `url` | Return format (`url` or `b64_json`) | ## Supported Models | Model | Provider | Sizes | Max Images | Pricing | | -------------------- | ----------------- | ------------------------------- | ---------- | ------------------------------------------ | | `dall-e-3` | OpenAI | 1024x1024, 1792x1024, 1024x1792 | 1 | $0.040/image (standard), $0.080/image (HD) | | `dall-e-2` | OpenAI | 256x256, 512x512, 1024x1024 | 10 | \$0.016-0.020/image | | `stable-diffusion-3` | Stability AI | 1024x1024, custom | 4 | \$0.035/image | | `flux-pro` | Black Forest Labs | 1024x1024, custom | 4 | \$0.055/image | | `flux-dev` | Black Forest Labs | 1024x1024, custom | 4 | \$0.025/image | | `ideogram-2` | Ideogram | 1024x1024, custom | 4 | \$0.040/image | | `imagen-3` | Google | 1024x1024 | 4 | \$0.040/image | ## Request Examples ### cURL ```bash theme={null} curl -X POST https://api.withperf.pro/v1/images/generations \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A serene Japanese garden with cherry blossoms at sunset, photorealistic", "model": "dall-e-3", "size": "1024x1024", "quality": "hd", "style": "vivid" }' ``` ### JavaScript ```javascript theme={null} const response = await fetch('https://api.withperf.pro/v1/images/generations', { method: 'POST', headers: { 'Authorization': 'Bearer pk_live_abc123', 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'A serene Japanese garden with cherry blossoms at sunset, photorealistic', model: 'dall-e-3', size: '1024x1024', quality: 'hd' }) }); const data = await response.json(); console.log(data.data[0].url); ``` ### Python ```python theme={null} import requests response = requests.post( 'https://api.withperf.pro/v1/images/generations', headers={ 'Authorization': 'Bearer pk_live_abc123', 'Content-Type': 'application/json' }, json={ 'prompt': 'A serene Japanese garden with cherry blossoms at sunset, photorealistic', 'model': 'dall-e-3', 'size': '1024x1024', 'quality': 'hd' } ) data = response.json() print(data['data'][0]['url']) ``` ## Response ### Success Response (200 OK) ```json theme={null} { "created": 1706123456, "data": [ { "url": "https://storage.withperf.pro/images/abc123.png", "revised_prompt": "A serene traditional Japanese garden featuring pink cherry blossom trees in full bloom, with petals gently falling over a stone path and koi pond, bathed in warm golden sunset light, photorealistic style" } ], "perf": { "request_id": "req_img_abc123", "model_used": "dall-e-3", "size": "1024x1024", "quality": "hd", "cost_usd": 0.080, "latency_ms": 5234 } } ``` ### Response Fields | Field | Type | Description | | ----------------------- | ------ | ------------------------------------------------------- | | `created` | number | Unix timestamp of generation | | `data` | array | Array of generated images | | `data[].url` | string | URL to the generated image (expires after 1 hour) | | `data[].b64_json` | string | Base64-encoded image (if `response_format: "b64_json"`) | | `data[].revised_prompt` | string | The actual prompt used (DALL-E 3 rewrites prompts) | | `perf.request_id` | string | Unique request identifier for tracking | | `perf.model_used` | string | Model that generated the image | | `perf.size` | string | Image dimensions | | `perf.quality` | string | Quality level used | | `perf.cost_usd` | number | Cost of this generation | | `perf.latency_ms` | number | Generation time in milliseconds | ## Prompt Best Practices ### Be Specific ```json theme={null} { "prompt": "A red 1965 Ford Mustang convertible parked in front of a neon-lit diner at night, rain-wet street reflecting the lights, cinematic photography style" } ``` ### Specify Style ```json theme={null} { "prompt": "Portrait of a wise elderly wizard with a long white beard, oil painting style, dramatic lighting, renaissance art influence" } ``` ### Use Negative Guidance For models that support it, describe what you don't want: ```json theme={null} { "prompt": "A clean modern kitchen interior, minimalist design, no people, no text, professional architectural photography" } ``` ## Error Responses ### 400 Bad Request ```json theme={null} { "error": { "type": "invalid_request", "message": "prompt is required", "param": "prompt" } } ``` ### 400 Content Policy Violation ```json theme={null} { "error": { "type": "content_policy_violation", "message": "Your request was rejected as a result of our safety system." } } ``` ### 429 Rate Limit ```json theme={null} { "error": { "type": "rate_limit_exceeded", "message": "Rate limit exceeded for image generation", "retry_after": 60 } } ``` ## Rate Limits | Tier | Images/Minute | Images/Day | | ---------- | ------------- | ---------- | | Free | 5 | 50 | | Pro | 30 | 1,000 | | Enterprise | Custom | Custom | ## Related Endpoints * [Chat API (with Vision)](./chat) - Send images to models for analysis * [Video Generation](./video) - Generate video content * [Audio Generation](./audio) - Text-to-speech and transcription # null Source: https://docs.withperf.pro/api-reference/logs # Logs API Reference The Logs API provides access to call history for debugging and analysis. > **Note**: Response schemas shown are illustrative. Actual responses may vary. ## Authentication Requires API key authentication: ``` Authorization: Bearer YOUR_API_KEY ``` ## Endpoints | Endpoint | Description | | ---------------------------------------- | ------------------------------ | | [GET /v1/logs](#list-logs) | Retrieve paginated call logs | | [GET /v1/logs/summary](#logs-summary) | Get summary statistics | | [GET /v1/logs/:call\_id](#get-log-by-id) | Retrieve specific call details | *** ## List Logs Retrieve paginated call logs with filtering and sorting options. ### Endpoint ``` GET https://api.withperf.pro/v1/logs ``` ### Query Parameters | Parameter | Type | Default | Description | | -------------- | ------- | ----------- | ---------------------------------------------------- | | `limit` | number | `50` | Number of logs to return (1-1000) | | `offset` | number | `0` | Number of logs to skip for pagination | | `start_date` | string | `-7 days` | ISO 8601 start date | | `end_date` | string | `now` | ISO 8601 end date | | `task_type` | string | `null` | Filter by task type | | `model` | string | `null` | Filter by model used | | `min_cost` | number | `null` | Minimum cost in USD | | `max_cost` | number | `null` | Maximum cost in USD | | `success_only` | boolean | `false` | Only show successful calls | | `failed_only` | boolean | `false` | Only show failed calls | | `sort_by` | string | `timestamp` | Sort field: `timestamp`, `cost`, `latency`, `tokens` | | `sort_order` | string | `desc` | Sort order: `asc` or `desc` | ### Example Request ```bash theme={null} curl "https://api.withperf.pro/v1/logs?limit=10&task_type=extraction&sort_by=cost&sort_order=desc" \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "total": 12456, "limit": 10, "offset": 0, "logs": [ { "call_id": "call_abc123xyz", "timestamp": "2024-01-30T14:32:15.234Z", "prompt": { "text": "Extract name, email, phone from: John Doe...", "hash": "a1b2c3d4e5f6", "length": 147 }, "classification": { "task_type": "extraction", "complexity_score": 0.34, "confidence": 0.92 }, "routing": { "model_selected": "gpt-4o-mini", "fallback_model": "claude-haiku-4-5", "routing_reason": "Optimal for structured data extraction", "provider": "openai" }, "execution": { "latency_ms": 567, "input_tokens": 47, "output_tokens": 28, "total_tokens": 75, "cost_usd": 0.00023, "started_at": "2024-01-30T14:32:15.234Z", "completed_at": "2024-01-30T14:32:15.801Z" }, "quality": { "validation_passed": true, "retry_count": 0, "fallback_used": false, "cost_warning_triggered": false, "output_quality_score": 0.94 }, "output": { "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\",\"phone\":\"555-1234\"}", "length": 67, "format_valid": true }, "metadata": { "user_id": "user_789", "session_id": "sess_xyz", "api_key_hash": "hash_abc", "custom": { "feature": "contact_extraction", "version": "v2" } } } ], "pagination": { "has_more": true, "next_offset": 10 } } ``` *** ## Logs Summary Get aggregated statistics for your logs. ### Endpoint ``` GET https://api.withperf.pro/v1/logs/summary ``` ### Query Parameters | Parameter | Type | Default | Description | | ------------ | ------ | --------- | ------------------- | | `start_date` | string | `-7 days` | ISO 8601 start date | | `end_date` | string | `now` | ISO 8601 end date | ### Example Request ```bash theme={null} curl "https://api.withperf.pro/v1/logs/summary" \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "period": { "start_date": "2024-01-24T00:00:00Z", "end_date": "2024-01-30T23:59:59Z", "days": 7 }, "summary": { "total_calls": 8234, "successful_calls": 8121, "failed_calls": 113, "success_rate": 0.986, "total_cost_usd": 42.34, "avg_cost_per_call": 0.00514, "total_tokens": 1234567, "avg_latency_ms": 1245 }, "by_model": { "gpt-4o-mini": { "calls": 4234, "percentage": 0.514, "total_cost_usd": 9.87, "avg_latency_ms": 834 }, "claude-sonnet-4-5": { "calls": 3456, "percentage": 0.420, "total_cost_usd": 28.93, "avg_latency_ms": 1456 }, "gpt-4o": { "calls": 544, "percentage": 0.066, "total_cost_usd": 3.54, "avg_latency_ms": 1876 } }, "by_task_type": { "extraction": 2801, "classification": 1893, "summarization": 1483, "reasoning": 988, "code": 658, "writing": 411 }, "quality_metrics": { "avg_quality_score": 0.92, "validation_pass_rate": 0.987, "retry_rate": 0.023, "fallback_rate": 0.034 } } ``` *** ## Get Log by ID Retrieve detailed information for a specific call. ### Endpoint ``` GET https://api.withperf.pro/v1/logs/:call_id ``` ### Example Request ```bash theme={null} curl https://api.withperf.pro/v1/logs/call_abc123xyz \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "call_id": "call_abc123xyz", "timestamp": "2024-01-30T14:32:15.234Z", "prompt": { "text": "Extract structured data from the following text...", "hash": "a1b2c3d4e5f6", "embedding": [0.123, -0.456, 0.789, ...], "length": 247, "message_count": 1 }, "classification": { "task_type": "extraction", "complexity_score": 0.34, "confidence": 0.92, "detected_format": "json" }, "routing": { "model_selected": "gpt-4o-mini", "fallback_model": "claude-haiku-4-5", "routing_reason": "Optimal cost/quality for structured extraction", "provider": "openai", "decision_factors": { "task_match": 0.92, "cost_efficiency": 0.87, "historical_performance": 0.89 } }, "execution": { "latency_ms": 567, "input_tokens": 47, "output_tokens": 28, "total_tokens": 75, "cost_usd": 0.00023, "started_at": "2024-01-30T14:32:15.234Z", "completed_at": "2024-01-30T14:32:15.801Z", "attempts": [ { "attempt_number": 1, "model": "gpt-4o-mini", "success": true, "latency_ms": 567, "cost_usd": 0.00023 } ] }, "quality": { "validation_passed": true, "retry_count": 0, "fallback_used": false, "cost_warning_triggered": false, "output_quality_score": 0.94, "validation_checks": { "format_valid": true, "no_refusal": true, "no_disclaimer": true, "completeness": true } }, "output": { "text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\",\"phone\":\"555-1234\"}", "length": 67, "format_valid": true, "parsed_json": { "name": "John Doe", "email": "john@example.com", "phone": "555-1234" } }, "context": { "provider_health": { "openai": { "error_rate": 0.002, "avg_latency_ms": 1234, "status": "healthy" } }, "budget": { "max_cost_per_call": 0.01, "cost_warning_threshold": 0.008 } }, "metadata": { "user_id": "user_789", "session_id": "sess_xyz", "api_key_hash": "hash_abc", "request_ip": "203.0.113.42", "user_agent": "Mozilla/5.0...", "custom": { "feature": "contact_extraction", "version": "v2", "experiment_id": "exp_123" } }, "shadow_call": { "executed": true, "shadow_model": "claude-haiku-4-5", "shadow_cost_usd": 0.00019, "shadow_latency_ms": 423, "quality_comparison": { "primary_score": 0.94, "shadow_score": 0.96, "optimal_choice": "shadow" } } } ``` *** ## Use Cases ### Debugging Failed Calls ```python theme={null} # Find all failed calls response = requests.get( "https://api.withperf.pro/v1/logs?failed_only=true&limit=100", headers={"Authorization": f"Bearer {API_KEY}"} ) logs = response.json()['logs'] for log in logs: print(f"Failed call: {log['call_id']}") print(f" Task: {log['classification']['task_type']}") print(f" Model: {log['routing']['model_selected']}") print(f" Retries: {log['quality']['retry_count']}") print(f" Fallback used: {log['quality']['fallback_used']}") ``` ### Cost Analysis ```python theme={null} # Find expensive calls response = requests.get( "https://api.withperf.pro/v1/logs?min_cost=0.01&sort_by=cost&sort_order=desc&limit=50", headers={"Authorization": f"Bearer {API_KEY}"} ) for log in response.json()['logs']: print(f"${log['execution']['cost_usd']:.4f} - {log['prompt']['text'][:50]}...") ``` ### Quality Monitoring ```python theme={null} # Find calls that required retries response = requests.get( "https://api.withperf.pro/v1/logs?limit=1000", headers={"Authorization": f"Bearer {API_KEY}"} ) retry_calls = [ log for log in response.json()['logs'] if log['quality']['retry_count'] > 0 ] print(f"Retry rate: {len(retry_calls) / len(response.json()['logs']):.1%}") ``` ### Export for Analysis ```python theme={null} import csv from datetime import datetime, timedelta # Export last 30 days to CSV start_date = (datetime.now() - timedelta(days=30)).isoformat() all_logs = [] offset = 0 limit = 1000 while True: response = requests.get( f"https://api.withperf.pro/v1/logs?start_date={start_date}&limit={limit}&offset={offset}", headers={"Authorization": f"Bearer {API_KEY}"} ).json() all_logs.extend(response['logs']) if not response['pagination']['has_more']: break offset += limit # Write to CSV with open('perf_logs.csv', 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=[ 'call_id', 'timestamp', 'task_type', 'model', 'cost_usd', 'latency_ms', 'success' ]) writer.writeheader() for log in all_logs: writer.writerow({ 'call_id': log['call_id'], 'timestamp': log['timestamp'], 'task_type': log['classification']['task_type'], 'model': log['routing']['model_selected'], 'cost_usd': log['execution']['cost_usd'], 'latency_ms': log['execution']['latency_ms'], 'success': log['quality']['validation_passed'] }) ``` *** ## Log Retention | Tier | Retention Period | Export Available | | ---------- | ---------------------- | ------------------ | | Free | 7 days | JSON | | Pro | 90 days | JSON, CSV | | Enterprise | Custom (up to 2 years) | JSON, CSV, Parquet | *** ## Filtering Best Practices ### 1. Use Date Ranges for Performance ```bash theme={null} # Good - specific date range curl "https://api.withperf.pro/v1/logs?start_date=2024-01-01&end_date=2024-01-07" # Avoid - fetching all logs curl "https://api.withperf.pro/v1/logs?limit=10000" ``` ### 2. Paginate Large Results ```python theme={null} def get_all_logs(start_date, end_date): all_logs = [] offset = 0 limit = 1000 while True: response = requests.get( f"{API_URL}/v1/logs", params={ 'start_date': start_date, 'end_date': end_date, 'limit': limit, 'offset': offset }, headers={'Authorization': f'Bearer {API_KEY}'} ).json() all_logs.extend(response['logs']) if not response['pagination']['has_more']: break offset += limit time.sleep(0.1) # Rate limiting return all_logs ``` ### 3. Combine Filters Efficiently ```bash theme={null} # Efficient - multiple filters reduce result set curl "https://api.withperf.pro/v1/logs?task_type=extraction&model=gpt-4o-mini&success_only=true&limit=100" ``` *** ## Rate Limits | Tier | Requests/Minute | Max Limit per Request | | ---------- | --------------- | --------------------- | | Free | 30 | 100 | | Pro | 120 | 1000 | | Enterprise | 600 | 10000 | *** ## Privacy & Security ### Data Handling * **Prompt Storage**: Full prompts stored for retention period * **Output Storage**: Full outputs stored for retention period * **PII Detection**: Automatic flagging (Enterprise) * **Encryption**: AES-256 at rest, TLS 1.3 in transit ### GDPR Compliance Delete user data on request: ```bash theme={null} curl -X DELETE https://api.withperf.pro/v1/logs/user/user_12345 \ -H "Authorization: Bearer pk_live_abc123" ``` ### Export User Data ```bash theme={null} curl "https://api.withperf.pro/v1/logs?user_id=user_12345" \ -H "Authorization: Bearer pk_live_abc123" \ > user_data.json ``` *** ## Related Resources * [Metrics API](./metrics) - For aggregated analytics * [Dashboard](../platform/dashboard) - Visual log exploration * [Best Practices](../resources/best-practices) - Optimization tips ## Support * **Email**: [support@withperf.pro](mailto:support@withperf.pro) * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) # null Source: https://docs.withperf.pro/api-reference/metrics # Metrics API Reference The Metrics API provides analytics and performance data for your LLM usage, enabling you to track costs, optimize routing, and measure performance. > **Note**: Response schemas shown are illustrative. Actual responses may vary. ## Authentication All metrics endpoints require API key authentication: ``` Authorization: Bearer YOUR_API_KEY ``` ## Endpoints Overview | Endpoint | Description | | --------------------------------------------------- | ------------------------------- | | [GET /v1/metrics/overview](#overview-metrics) | High-level performance summary | | [GET /v1/metrics/customer](#customer-metrics) | Your account-specific metrics | | [GET /v1/metrics/performance](#performance-metrics) | Model performance by task type | | [GET /v1/metrics/failures](#failure-analysis) | Failure mode analysis | | [GET /v1/metrics/providers](#provider-health) | Provider health and reliability | | [GET /v1/metrics/daily](#daily-metrics) | Daily aggregated metrics | *** ## Overview Metrics Get a high-level summary of your routing performance. ### Endpoint ``` GET https://api.withperf.pro/v1/metrics/overview ``` ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | -------------------------------- | | `days` | number | `7` | Number of days to analyze (1-90) | ### Example Request ```bash theme={null} curl https://api.withperf.pro/v1/metrics/overview?days=30 \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "period": { "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-30T23:59:59Z", "days": 30 }, "summary": { "total_calls": 45678, "total_cost_usd": 234.56, "avg_cost_per_call": 0.00514, "avg_latency_ms": 1245, "success_rate": 0.987 }, "routing": { "accuracy": 0.923, "fallback_rate": 0.034, "retry_rate": 0.012 }, "models": { "gpt-4o-mini": { "calls": 23456, "percentage": 0.514, "avg_cost": 0.00234, "avg_latency_ms": 834 }, "claude-sonnet-4-5": { "calls": 18234, "percentage": 0.399, "avg_cost": 0.00876, "avg_latency_ms": 1456 }, "gpt-4o": { "calls": 3988, "percentage": 0.087, "avg_cost": 0.01234, "avg_latency_ms": 1876 } }, "task_distribution": { "extraction": 0.34, "classification": 0.23, "summarization": 0.18, "reasoning": 0.12, "code": 0.08, "writing": 0.05 } } ``` *** ## Customer Metrics Get detailed metrics specific to your API key and usage patterns. ### Endpoint ``` GET https://api.withperf.pro/v1/metrics/customer ``` ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | -------------------------------- | | `days` | number | `30` | Number of days to analyze (1-90) | ### Example Request ```bash theme={null} curl https://api.withperf.pro/v1/metrics/customer?days=30 \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "account_id": "acct_abc123", "period": { "start_date": "2024-01-01T00:00:00Z", "end_date": "2024-01-30T23:59:59Z" }, "usage": { "total_calls": 12456, "total_tokens": 5678901, "total_cost_usd": 123.45, "avg_calls_per_day": 414 }, "cost_analysis": { "current_month": 123.45, "previous_month": 156.78, "change_percentage": -21.2, "projected_month_end": 145.67, "vs_gpt4o_only": { "cost_if_gpt4o": 412.34, "savings_usd": 288.89, "savings_percentage": 70.1 } }, "quality_preferences": { "cost_vs_quality_ratio": 0.65, "latency_sensitivity": 0.42, "preferred_models": ["gpt-4o-mini", "claude-sonnet-4-5"] }, "task_profile": { "dominant_tasks": ["extraction", "classification"], "complexity_avg": 0.42, "task_overrides": { "reasoning": "claude-sonnet-4-5" } }, "behavior_insights": { "peak_hours": [9, 10, 11, 14, 15, 16], "avg_conversation_length": 4.2, "retry_rate": 0.023, "cost_ceiling_hit_rate": 0.087 } } ``` *** ## Performance Metrics Analyze model performance broken down by task type. ### Endpoint ``` GET https://api.withperf.pro/v1/metrics/performance ``` ### Query Parameters | Parameter | Type | Default | Description | | ----------- | ------ | ------- | -------------------------------- | | `days` | number | `7` | Number of days to analyze (1-90) | | `task_type` | string | `null` | Filter by specific task type | ### Example Request ```bash theme={null} curl "https://api.withperf.pro/v1/metrics/performance?days=7&task_type=extraction" \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "period": { "start_date": "2024-01-24T00:00:00Z", "end_date": "2024-01-30T23:59:59Z" }, "by_task_type": { "extraction": { "total_calls": 5678, "models": { "gpt-4o-mini": { "calls": 4234, "success_rate": 0.987, "avg_cost_usd": 0.00123, "avg_latency_ms": 567, "quality_score": 0.92 }, "claude-haiku-4-5": { "calls": 1444, "success_rate": 0.991, "avg_cost_usd": 0.00098, "avg_latency_ms": 423, "quality_score": 0.94 } }, "optimal_model": "claude-haiku-4-5", "optimal_reason": "Best cost/quality balance for structured extraction" }, "reasoning": { "total_calls": 1234, "models": { "claude-sonnet-4-5": { "calls": 987, "success_rate": 0.956, "avg_cost_usd": 0.00876, "avg_latency_ms": 1876, "quality_score": 0.95 }, "gpt-4o": { "calls": 247, "success_rate": 0.943, "avg_cost_usd": 0.01234, "avg_latency_ms": 2134, "quality_score": 0.93 } }, "optimal_model": "claude-sonnet-4-5", "optimal_reason": "Superior reasoning with better cost efficiency" } }, "recommendations": [ { "task_type": "extraction", "current_model": "gpt-4o-mini", "recommended_model": "claude-haiku-4-5", "potential_savings_usd": 14.23, "quality_improvement": 0.02 } ] } ``` *** ## Failure Analysis Understand why calls fail and which models are most reliable. ### Endpoint ``` GET https://api.withperf.pro/v1/metrics/failures ``` ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | -------------------------------- | | `days` | number | `7` | Number of days to analyze (1-90) | ### Example Request ```bash theme={null} curl https://api.withperf.pro/v1/metrics/failures?days=7 \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "period": { "start_date": "2024-01-24T00:00:00Z", "end_date": "2024-01-30T23:59:59Z" }, "summary": { "total_failures": 234, "failure_rate": 0.013, "retries_succeeded": 156, "fallback_succeeded": 67, "unrecoverable": 11 }, "by_failure_mode": { "format_violation": { "count": 89, "percentage": 0.38, "affected_models": ["gpt-4o-mini", "claude-haiku-4-5"], "common_triggers": ["complex JSON structures", "nested arrays"] }, "refusal": { "count": 56, "percentage": 0.24, "affected_models": ["gpt-4o", "claude-sonnet-4-5"], "common_triggers": ["policy violations", "ambiguous requests"] }, "hallucination": { "count": 34, "percentage": 0.15, "affected_models": ["gpt-4o-mini"], "common_triggers": ["data extraction from noise", "edge cases"] }, "incomplete": { "count": 31, "percentage": 0.13, "affected_models": ["claude-haiku-4-5"], "common_triggers": ["max tokens exceeded", "complex outputs"] }, "reasoning_error": { "count": 24, "percentage": 0.10, "affected_models": ["gpt-4o-mini"], "common_triggers": ["multi-step logic", "mathematical reasoning"] } }, "by_model": { "gpt-4o-mini": { "total_calls": 8234, "failures": 123, "failure_rate": 0.015, "top_failure_modes": ["hallucination", "format_violation"] }, "claude-sonnet-4-5": { "total_calls": 5678, "failures": 67, "failure_rate": 0.012, "top_failure_modes": ["refusal", "format_violation"] } }, "mitigation_recommendations": [ { "issue": "High format_violation rate for complex JSON", "recommendation": "Add schema validation to prompts", "expected_improvement": "40% reduction in failures" }, { "issue": "Hallucinations in data extraction", "recommendation": "Use Claude Haiku for structured extraction", "expected_improvement": "60% reduction in hallucinations" } ] } ``` *** ## Provider Health Monitor provider reliability and performance trends. ### Endpoint ``` GET https://api.withperf.pro/v1/metrics/providers ``` ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | ---------------------------------- | | `hours` | number | `24` | Number of hours to analyze (1-168) | ### Example Request ```bash theme={null} curl https://api.withperf.pro/v1/metrics/providers?hours=24 \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "period": { "start_time": "2024-01-30T10:00:00Z", "end_time": "2024-01-31T10:00:00Z", "hours": 24 }, "providers": { "openai": { "status": "operational", "uptime": 0.998, "avg_latency_ms": 1234, "p95_latency_ms": 2345, "p99_latency_ms": 3456, "error_rate": 0.002, "rate_limit_hits": 12, "incidents": [] }, "anthropic": { "status": "operational", "uptime": 0.999, "avg_latency_ms": 1567, "p95_latency_ms": 2876, "p99_latency_ms": 4123, "error_rate": 0.001, "rate_limit_hits": 3, "incidents": [] } }, "trends": { "openai": { "latency_trend": "stable", "error_trend": "improving", "vs_previous_period": { "latency_change": -0.05, "error_change": -0.43 } }, "anthropic": { "latency_trend": "stable", "error_trend": "stable", "vs_previous_period": { "latency_change": 0.02, "error_change": 0.00 } } }, "recommendations": [ { "provider": "openai", "message": "Experiencing slightly elevated latency during peak hours (2-4pm UTC)", "action": "Consider using Anthropic for latency-sensitive tasks during this window" } ] } ``` *** ## Daily Metrics Get daily aggregated metrics for trend analysis and reporting. ### Endpoint ``` GET https://api.withperf.pro/v1/metrics/daily ``` ### Query Parameters | Parameter | Type | Default | Description | | --------- | ------ | ------- | --------------------------------- | | `days` | number | `30` | Number of days to retrieve (1-90) | ### Example Request ```bash theme={null} curl https://api.withperf.pro/v1/metrics/daily?days=30 \ -H "Authorization: Bearer pk_live_abc123" ``` ### Response ```json theme={null} { "period": { "start_date": "2024-01-01", "end_date": "2024-01-30" }, "daily_data": [ { "date": "2024-01-01", "total_calls": 456, "total_cost_usd": 2.34, "avg_latency_ms": 1234, "success_rate": 0.987, "model_distribution": { "gpt-4o-mini": 234, "claude-sonnet-4-5": 178, "gpt-4o": 44 }, "task_distribution": { "extraction": 145, "classification": 98, "summarization": 76, "reasoning": 54, "code": 45, "writing": 38 } }, { "date": "2024-01-02", "total_calls": 523, "total_cost_usd": 2.87, "avg_latency_ms": 1187, "success_rate": 0.991, "model_distribution": { "gpt-4o-mini": 289, "claude-sonnet-4-5": 198, "gpt-4o": 36 }, "task_distribution": { "extraction": 167, "classification": 112, "summarization": 89, "reasoning": 61, "code": 52, "writing": 42 } } ], "trends": { "calls_trend": "increasing", "cost_trend": "stable", "quality_trend": "improving", "avg_daily_growth": 0.034 } } ``` *** ## Use Cases ### Dashboard Building ```typescript theme={null} // Fetch overview for homepage dashboard const overview = await fetch( 'https://api.withperf.pro/v1/metrics/overview?days=7', { headers: { Authorization: `Bearer ${API_KEY}` } } ).then(r => r.json()); // Display KPIs displayKPI('Total Calls', overview.summary.total_calls); displayKPI('Avg Cost', `$${overview.summary.avg_cost_per_call.toFixed(5)}`); displayKPI('Success Rate', `${(overview.summary.success_rate * 100).toFixed(1)}%`); ``` ### Cost Monitoring ```python theme={null} # Alert if costs exceed threshold customer = requests.get( "https://api.withperf.pro/v1/metrics/customer?days=30", headers={"Authorization": f"Bearer {API_KEY}"} ).json() projected_cost = customer['cost_analysis']['projected_month_end'] if projected_cost > BUDGET_LIMIT: send_alert(f"Projected cost ${projected_cost} exceeds budget ${BUDGET_LIMIT}") ``` ### Performance Optimization ```python theme={null} # Find optimization opportunities performance = requests.get( "https://api.withperf.pro/v1/metrics/performance?days=30", headers={"Authorization": f"Bearer {API_KEY}"} ).json() for rec in performance['recommendations']: print(f"Switch {rec['task_type']} to {rec['recommended_model']}") print(f" Savings: ${rec['potential_savings_usd']:.2f}") print(f" Quality: +{rec['quality_improvement']:.1%}") ``` ## Rate Limits Metrics API has separate rate limits: | Tier | Requests/Minute | | ---------- | --------------- | | Free | 10 | | Pro | 60 | | Enterprise | 300 | ## Best Practices 1. **Cache metrics data**: Results change slowly, cache for 5-15 minutes 2. **Use appropriate time ranges**: Longer periods for trends, shorter for real-time monitoring 3. **Set up alerts**: Monitor `projected_month_end` and `failure_rate` 4. **Review weekly**: Check `recommendations` for optimization opportunities ## Related Resources * [Dashboard Documentation](../platform/dashboard) * [Analytics Guide](../platform/analytics) * [Best Practices](../resources/best-practices) ## Support * **Email**: [support@withperf.pro](mailto:support@withperf.pro) * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) # Policies API Source: https://docs.withperf.pro/api-reference/policies Configure routing and content policies for governance and compliance # Policies API Policies allow you to control how Perf routes requests and handles content. Set model preferences, enforce cost limits, detect PII, and filter sensitive terms. ## Why Policies? * **Cost Control**: Set budget limits and prefer cheaper models * **Compliance**: Block specific providers or require certain models * **Security**: Detect and redact PII from outputs * **Governance**: Monitor and control content with term filtering ## Policy Types | Type | Description | Available Tiers | | --------- | --------------------------------- | ----------------------------- | | `routing` | Model selection and routing rules | All tiers | | `content` | PII detection and term filtering | Pro, Growth, Team, Enterprise | ## Policy Templates API Get pre-built policy templates to quickly enable common configurations. ### List Templates ``` GET https://api.withperf.pro/v1/policies/templates ``` **Response:** ```json theme={null} { "templates": [ { "slug": "budget-mode", "name": "Budget Mode", "description": "Aggressive cost optimization - prefer cheapest models", "category": "cost", "policy_type": "routing", "config": { "prefer_cheaper_models": true, "cost_ceiling_usd": 0.01 } }, { "slug": "pii-protection", "name": "PII Protection", "description": "Detect and redact PII from outputs", "category": "compliance", "policy_type": "content", "config": { "pii_detection": { "enabled": true, "types": ["ssn", "credit_card", "email"], "action": "redact" } } } ] } ``` ### Get Template by Slug ``` GET https://api.withperf.pro/v1/policies/templates/{slug} ``` **Example:** ```bash theme={null} curl https://api.withperf.pro/v1/policies/templates/budget-mode \ -H "Authorization: Bearer pk_live_abc123" ``` ## Available Templates ### Routing Templates | Template | Slug | Category | Description | | ---------------- | ------------------ | ----------- | ---------------------------------------- | | Budget Mode | `budget-mode` | cost | Prefer cheapest models, set cost ceiling | | Performance Mode | `performance-mode` | performance | Optimize for low latency | | Quality Mode | `quality-mode` | performance | Maximize output quality with best models | | No OpenAI | `no-openai` | compliance | Block all OpenAI models | | No Anthropic | `no-anthropic` | compliance | Block all Anthropic models | | Google Only | `google-only` | compliance | Only use Google Gemini models | ### Content Templates (Pro+) | Template | Slug | Category | Description | | -------------------------- | ---------------------------- | ---------- | ----------------------------------------- | | PII Protection | `pii-protection` | compliance | Detect and redact PII from outputs | | Healthcare Compliance | `healthcare-compliance` | compliance | Medical context awareness + PII detection | | Child-Safe Content | `child-safe` | compliance | Age-appropriate content enforcement | | Professional Communication | `professional-communication` | compliance | Business-appropriate tone | | Custom Terms Filter | `custom-terms-filter` | compliance | Block or require specific terms | ## Routing Policy Options Configure how Perf selects models: | Option | Type | Description | | ----------------------- | --------- | ---------------------------------------------------- | | `model_allow_list` | string\[] | Only allow these specific models | | `model_block_list` | string\[] | Block these models from selection | | `provider_allow_list` | string\[] | Only use these providers (openai, anthropic, google) | | `provider_block_list` | string\[] | Block these providers | | `cost_ceiling_usd` | number | Maximum cost per API call | | `prefer_cheaper_models` | boolean | Prefer lower-cost models when quality is similar | | `latency_target_ms` | number | Target maximum latency | | `prefer_faster_models` | boolean | Prefer lower-latency models | | `on_violation` | string | Action: `info`, `warn`, `soft_block`, `hard_block` | **Example - Google Only Policy:** ```json theme={null} { "name": "Google Only", "policy_type": "routing", "config": { "provider_allow_list": ["google"], "on_violation": "hard_block" } } ``` ## Content Policy Options Configure content filtering and PII detection: | Option | Type | Description | | -------------------------------- | --------- | -------------------------------------- | | `evaluate_output` | boolean | Evaluate model output (default: true) | | `pii_detection.enabled` | boolean | Enable PII detection | | `pii_detection.types` | string\[] | PII types to detect | | `pii_detection.action` | string | Action: `warn`, `redact`, `block` | | `pii_detection.redaction_format` | string | Redaction text (default: `[REDACTED]`) | | `blocked_terms.enabled` | boolean | Enable blocked term detection | | `blocked_terms.terms` | string\[] | List of terms to block | | `blocked_terms.action` | string | Action: `warn`, `redact`, `block` | | `on_violation` | string | Overall violation action | **Example - PII Protection Policy:** ```json theme={null} { "name": "PII Protection", "policy_type": "content", "config": { "evaluate_output": true, "pii_detection": { "enabled": true, "types": ["ssn", "credit_card", "email", "phone_us"], "action": "redact", "redaction_format": "[REDACTED]" }, "on_violation": "soft_block" } } ``` ## Supported PII Types | Type | Pattern | Description | | --------------- | ----------------------------------------- | ----------------------------------- | | `ssn` | XXX-XX-XXXX | Social Security Numbers | | `ssn_no_dash` | XXXXXXXXX | SSN without dashes | | `credit_card` | 13-19 digits | Credit cards (with Luhn validation) | | `email` | [user@domain.com](mailto:user@domain.com) | Email addresses | | `phone_us` | (XXX) XXX-XXXX | US phone numbers | | `ip_address` | X.X.X.X | IPv4 addresses | | `date_of_birth` | MM/DD/YYYY | Dates of birth | ## Violation Actions | Action | Description | | ------------ | ------------------------------------------------------------------- | | `info` | Log only (audit mode) - request proceeds | | `warn` | Log warning - request proceeds with warning in response | | `soft_block` | Apply modifications (redact PII, override model) - request proceeds | | `hard_block` | Reject request with 403 error | ## Policy Blocked Response When a request is blocked by a `hard_block` policy: ```json theme={null} { "error": "Request blocked by policy", "code": "POLICY_BLOCKED", "message": "Request blocked by policy: Model gpt-4o-mini is not in allow list", "policy_evaluation": { "result": "hard_block", "violations": [ { "policy_name": "Google Only", "rule": "model_allow_list", "message": "Model gpt-4o-mini is not in allow list" } ] } } ``` ## Managing Policies via Dashboard Policies are configured per-project in the Perf Dashboard: 1. Navigate to **Configure > Policies** 2. Click **Create Policy** or **Apply Template** 3. Configure rules and violation actions 4. Set priority (lower number = higher priority) 5. Enable the policy Multiple policies can be active simultaneously. They are evaluated in priority order. ## Plan Limits | Tier | Max Policies | Policy Types | | ---------- | ------------ | ---------------- | | Starter | 3 | routing | | Pro | 10 | routing, content | | Growth | 25 | routing, content | | Team | 50 | routing, content | | Enterprise | 1000 | all types | ## Related * [Chat API](./chat) - See policy evaluation in responses * [Schema Enforcement](./schemas) - Validate JSON output structure # Schema Enforcement Source: https://docs.withperf.pro/api-reference/schemas Validate, auto-repair, and semantically correct LLM outputs against JSON schemas # Schema Enforcement Schema Enforcement ensures your LLM outputs conform to a defined JSON schema. Perf validates responses, auto-repairs common issues, applies semantic corrections (like fixing negative prices), and guarantees you receive properly structured data. ## Why Schema Enforcement? LLMs are unreliable at producing consistent JSON: * Missing required fields * Wrong data types (string instead of number) * Extra fields not in schema * Malformed JSON syntax * Markdown code blocks wrapping JSON Perf's Schema Enforcement: * **Validates** outputs against your JSON Schema * **Auto-repairs** common issues (type coercion, format fixes) * **Semantic correction** fixes domain-specific errors (negative prices, invalid ratings) * **Retries** with better prompts if validation fails * **Guarantees** schema-compliant responses ## Using Schemas in Requests ### Inline Schema Pass a JSON Schema directly in your request: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Extract contact info: John Smith, john@example.com, 555-1234" } ], "schema": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "phone": { "type": "string" } }, "required": ["name", "email"] } }' ``` ### Schema by ID Reference a saved schema by ID or slug: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [{"role": "user", "content": "Extract contact info..."}], "schema_id": "contact-extraction" }' ``` ### Project Default Schema Set a default schema for your project in the Dashboard. All requests without an explicit schema will use it. ## Schema Parameters | Parameter | Type | Description | | --------------- | ------- | ------------------------------------------------------------ | | `schema` | object | Inline JSON Schema (Draft 2020-12) | | `schema_id` | string | ID or slug of a saved schema | | `schema_strict` | boolean | Disable auto-repair, fail on any mismatch (default: `false`) | ## Response When schema enforcement is active, responses include validation metadata: ```json theme={null} { "choices": [{ "message": { "content": "{\"name\": \"John Smith\", \"email\": \"john@example.com\", \"phone\": \"555-1234\"}" } }], "perf": { "schema_validation": { "enabled": true, "passed": true, "repaired": false, "schema_source": "inline" } } } ``` ### Validation Fields | Field | Type | Description | | --------------- | ------- | ------------------------------------------- | | `enabled` | boolean | Schema validation was active | | `passed` | boolean | Final output passed validation | | `repaired` | boolean | Auto-repair was applied | | `schema_source` | string | `inline`, `schema_id`, or `project_default` | ## Auto-Repair When `schema_strict: false` (default), Perf attempts to repair common issues: | Issue | Repair Action | | ------------------------- | --------------------------------- | | String instead of number | Parse to number (`"42"` → `42`) | | Number instead of string | Convert to string (`42` → `"42"`) | | String instead of boolean | Parse (`"true"` → `true`) | | Markdown code blocks | Extract JSON from `json ... ` | | Extra whitespace | Trim and normalize | | Missing optional fields | Leave as undefined | ### Example: Auto-Repair in Action LLM returns: ```` Here's the extracted data: ```json {"name": "John", "age": "25"} ```` ```` With schema requiring `age` as number, Perf: 1. Extracts JSON from markdown 2. Coerces `"25"` → `25` 3. Returns valid JSON ## Semantic Validation Go beyond structural validation with semantic type hints. Add `x-semantic-type` to your schema fields to auto-correct common LLM errors like negative prices, out-of-range ratings, and invalid dates. ### Using x-semantic-type ```json { "type": "object", "properties": { "price": { "type": "number", "x-semantic-type": "currency_positive" }, "rating": { "type": "number", "x-semantic-type": "rating_1_5" }, "customer_age": { "type": "integer", "x-semantic-type": "human_age" } } } ```` ### Supported Semantic Types | Type | Description | Auto-Correction | | ------------------------- | ---------------------------------- | -------------------------- | | `currency_positive` | Prices, amounts (must be positive) | `-$999` → `$999` | | `currency_allow_negative` | Values that can be negative | Validates magnitude only | | `human_age` | Person ages (0-130) | Clamps to valid range | | `percentage` | Percentages (0-100 or 0-1) | Normalizes scale | | `rating_1_5` | 5-star ratings | `12` → `5` (clamps to max) | | `rating_1_10` | 10-point ratings | Clamps to 1-10 range | | `email` | Email addresses | Format validation | | `url` | URLs | Format validation | | `date_past` | Historical dates | Flags future dates | | `date_future` | Upcoming dates | Flags past dates | ### Example: Semantic Correction in Action Request with semantic types: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [{ "role": "user", "content": "Extract: iPhone 15 Pro costs -$999, rating 12/5 stars" }], "response_format": { "type": "json_schema", "json_schema": { "name": "product", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "price": { "type": "number", "x-semantic-type": "currency_positive" }, "rating": { "type": "number", "x-semantic-type": "rating_1_5" } }, "required": ["name", "price", "rating"] } } } }' ``` Response with corrections: ```json theme={null} { "choices": [{ "message": { "content": "{\"name\": \"iPhone 15 Pro\", \"price\": 999, \"rating\": 5}" } }], "perf": { "semantic_validation": { "result": "corrected", "corrections": [ { "field": "price", "original": -999, "corrected": 999, "reason": "currency_positive values must be non-negative", "confidence": 1.0 }, { "field": "rating", "original": 12, "corrected": 5, "reason": "rating_1_5 clamped to maximum value", "confidence": 1.0 } ], "latency_ms": 3.2 } } } ``` ### Semantic Validation Response Fields | Field | Type | Description | | ------------- | ------ | ---------------------------------- | | `result` | string | `pass`, `corrected`, or `failed` | | `corrections` | array | List of auto-corrections applied | | `warnings` | array | Non-blocking issues detected | | `latency_ms` | number | Validation latency in milliseconds | ## Strict Mode Enable strict mode to disable auto-repair: ```json theme={null} { "messages": [...], "schema": {...}, "schema_strict": true } ``` In strict mode: * No type coercion * No format repairs * Validation fails on any mismatch * Returns error if validation fails after retries ## Managing Schemas ### Create Schema (Dashboard API) ```bash theme={null} curl -X POST https://api.withperf.pro/v1/dashboard/projects/{projectId}/schemas \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Contact Extraction", "slug": "contact-extraction", "description": "Schema for extracting contact information", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "phone": { "type": "string" } }, "required": ["name", "email"] }, "is_default": false }' ``` ### List Schemas ```bash theme={null} curl https://api.withperf.pro/v1/dashboard/projects/{projectId}/schemas \ -H "Authorization: Bearer " ``` ### Test Schema Test a schema against sample data: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/dashboard/projects/{projectId}/schemas/test \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "schema": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] }, "data": {"name": "John"} }' ``` ## Supported JSON Schema Features Perf supports JSON Schema Draft 2020-12 with these features: ### Types * `string`, `number`, `integer`, `boolean`, `null` * `object`, `array` ### String Formats * `email`, `uri`, `date`, `date-time`, `uuid` ### Validation Keywords * `required`, `properties`, `additionalProperties` * `minLength`, `maxLength`, `pattern` * `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum` * `minItems`, `maxItems`, `uniqueItems` * `enum`, `const` ### Composition * `allOf`, `anyOf`, `oneOf` * `$ref` (local references only) ## Best Practices ### 1. Keep Schemas Simple ```json theme={null} { "type": "object", "properties": { "sentiment": { "enum": ["positive", "negative", "neutral"] }, "confidence": { "type": "number", "minimum": 0, "maximum": 1 } }, "required": ["sentiment", "confidence"] } ``` ### 2. Use Descriptive Names Add `title` and `description` to help the LLM understand: ```json theme={null} { "type": "object", "title": "Product Review Analysis", "properties": { "rating": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Star rating from 1-5" } } } ``` ### 3. Prefer Enums for Categories ```json theme={null} { "category": { "type": "string", "enum": ["bug", "feature", "question", "documentation"] } } ``` ### 4. Set Reasonable Defaults Use `default` for optional fields: ```json theme={null} { "properties": { "priority": { "type": "string", "enum": ["low", "medium", "high"], "default": "medium" } } } ``` ## Error Handling ### Validation Failed If validation fails after retries: ```json theme={null} { "error": { "type": "schema_validation_failed", "message": "Output failed schema validation after 2 attempts", "details": { "errors": [ { "path": "/email", "message": "must match format \"email\"" } ] } } } ``` ### Invalid Schema If your schema is invalid: ```json theme={null} { "error": { "type": "invalid_schema", "message": "Schema validation error: 'type' must be a string" } } ``` ## Plan Limits | Tier | Max Schemas | Auto-Repair | Semantic Validation | | ---------- | ----------- | ----------- | ------------------- | | Starter | 3 | No | No | | Pro | 10 | Yes | Yes | | Growth | 50 | Yes | Yes | | Enterprise | Unlimited | Yes | Yes | ## Related Endpoints * [Chat API](./chat) - Use schemas with chat completions * [Streaming API](./streaming) - Schema validation with streaming * [Logs API](./logs) - View validation history # Streaming API Source: https://docs.withperf.pro/api-reference/streaming Real-time streaming API documentation # Streaming API Reference The Streaming API provides real-time, token-by-token responses for building responsive chat interfaces and interactive applications. The streaming format is **OpenAI-compatible**, using Server-Sent Events (SSE). ## Endpoint ``` POST https://api.withperf.pro/v1/chat/stream ``` ## Authentication Include your API key in the Authorization header: ``` Authorization: Bearer YOUR_API_KEY ``` ## How It Works The Streaming API returns responses using Server-Sent Events (SSE), sending text chunks as they're generated rather than waiting for the complete response. ### Benefits * **Lower perceived latency**: Users see responses immediately * **Better UX**: Progressive rendering feels more responsive * **Real-time feedback**: Stop generation early if needed * **Streaming UI**: Perfect for chat interfaces ## Request Body Same as the [Chat API](./chat), but responses stream incrementally. This includes full support for multimodal content (images, audio, video, documents) in messages. ### Example Request ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat/stream \ -H "Authorization: Bearer pk_test_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Explain quantum computing in simple terms" } ], "max_cost_per_call": 0.01 }' ``` ## Response Format The response uses **OpenAI-compatible Server-Sent Events (SSE)** format: ``` data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1705312200,"model":"claude-sonnet-4-5-20250929","choices":[{"index":0,"delta":{"role":"assistant","content":"Quantum"},"finish_reason":null}]} data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1705312200,"model":"claude-sonnet-4-5-20250929","choices":[{"index":0,"delta":{"content":" computing"},"finish_reason":null}]} data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1705312200,"model":"claude-sonnet-4-5-20250929","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: [DONE] ``` ## Event Types ### Content Chunk Sent for each token or group of tokens. The first chunk includes the `role`, subsequent chunks only have `content`: ```json theme={null} { "id": "chatcmpl-abc123", "object": "chat.completion.chunk", "created": 1705312200, "model": "claude-sonnet-4-5-20250929", "choices": [ { "index": 0, "delta": { "role": "assistant", "content": "text fragment" }, "finish_reason": null } ] } ``` ### Final Chunk Sent when generation is complete with `finish_reason: "stop"`: ```json theme={null} { "id": "chatcmpl-abc123", "object": "chat.completion.chunk", "created": 1705312200, "model": "claude-sonnet-4-5-20250929", "choices": [ { "index": 0, "delta": {}, "finish_reason": "stop" } ] } ``` ### Done Signal After the final chunk, a `[DONE]` message indicates the stream is complete: ``` data: [DONE] ``` ## Client Implementation ### JavaScript/TypeScript ```typescript theme={null} async function streamChat(messages: Message[]) { const response = await fetch('https://api.withperf.pro/v1/chat/stream', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ messages }), }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); let fullText = ''; while (true) { const { done, value } = await reader!.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const payload = line.slice(6); // Check for [DONE] signal if (payload === '[DONE]') { console.log('Stream complete!'); break; } const data = JSON.parse(payload); const content = data.choices?.[0]?.delta?.content || ''; if (content) { fullText += content; console.log('Partial:', fullText); } // Check for finish_reason if (data.choices?.[0]?.finish_reason === 'stop') { console.log('Model:', data.model); } } } } return fullText; } ``` ### React Hook ```typescript theme={null} import { useState, useCallback } from 'react'; export function useStreamingChat() { const [content, setContent] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const [model, setModel] = useState(null); const streamMessage = useCallback(async (messages: Message[]) => { setContent(''); setIsStreaming(true); setModel(null); try { const response = await fetch('/api/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages }), }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader!.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const payload = line.slice(6); if (payload === '[DONE]') break; const data = JSON.parse(payload); const deltaContent = data.choices?.[0]?.delta?.content || ''; if (deltaContent) { setContent(prev => prev + deltaContent); } if (data.choices?.[0]?.finish_reason === 'stop') { setModel(data.model); } } } } } finally { setIsStreaming(false); } }, []); return { content, isStreaming, model, streamMessage }; } ``` ### Python ```python theme={null} import requests import json def stream_chat(messages): url = "https://api.withperf.pro/v1/chat/stream" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } payload = {"messages": messages} with requests.post(url, json=payload, headers=headers, stream=True) as response: full_text = "" for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: '): payload = line[6:] # Check for [DONE] signal if payload == '[DONE]': print("\n\nStream complete!") break data = json.loads(payload) content = data.get('choices', [{}])[0].get('delta', {}).get('content', '') if content: full_text += content print(content, end='', flush=True) # Check for finish_reason if data.get('choices', [{}])[0].get('finish_reason') == 'stop': print(f"\n\nModel: {data.get('model')}") return full_text # Usage messages = [{"role": "user", "content": "Tell me a story"}] result = stream_chat(messages) ``` ### Python Async ```python theme={null} import aiohttp import asyncio import json async def stream_chat_async(messages): url = "https://api.withperf.pro/v1/chat/stream" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } payload = {"messages": messages} async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as response: async for line in response.content: line = line.decode('utf-8').strip() if line.startswith('data: '): payload = line[6:] if payload == '[DONE]': yield {'done': True} break data = json.loads(payload) content = data.get('choices', [{}])[0].get('delta', {}).get('content', '') if content: yield content # Usage async def main(): messages = [{"role": "user", "content": "Explain AI"}] async for chunk in stream_chat_async(messages): if isinstance(chunk, str): print(chunk, end='', flush=True) else: print("\n\nDone!") asyncio.run(main()) ``` ### Go ```go theme={null} package main import ( "bufio" "bytes" "encoding/json" "fmt" "net/http" "strings" ) type StreamChunk struct { Choices []struct { Delta struct { Content string `json:"content"` } `json:"delta"` FinishReason *string `json:"finish_reason"` } `json:"choices"` Model string `json:"model"` } func streamChat(messages []Message) error { payload, _ := json.Marshal(map[string]interface{}{ "messages": messages, }) req, _ := http.NewRequest("POST", "https://api.withperf.pro/v1/chat/stream", bytes.NewBuffer(payload)) req.Header.Set("Authorization", "Bearer "+API_KEY) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "data: ") { payload := line[6:] // Check for [DONE] signal if payload == "[DONE]" { fmt.Println("\nDone!") break } var chunk StreamChunk json.Unmarshal([]byte(payload), &chunk) if len(chunk.Choices) > 0 { content := chunk.Choices[0].Delta.Content if content != "" { fmt.Print(content) } } } } return scanner.Err() } ``` ## React Component Example ```typescript theme={null} 'use client'; import { useState } from 'react'; export default function StreamingChat() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [streamingContent, setStreamingContent] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const sendMessage = async () => { if (!input.trim()) return; const userMessage = { role: 'user', content: input }; setMessages(prev => [...prev, userMessage]); setInput(''); setIsStreaming(true); setStreamingContent(''); try { const response = await fetch('/api/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [...messages, userMessage], }), }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); let fullContent = ''; while (true) { const { done, value } = await reader!.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const payload = line.slice(6); // Check for [DONE] signal if (payload === '[DONE]') { setMessages(prev => [ ...prev, { role: 'assistant', content: fullContent } ]); setStreamingContent(''); break; } const data = JSON.parse(payload); const deltaContent = data.choices?.[0]?.delta?.content || ''; if (deltaContent) { fullContent += deltaContent; setStreamingContent(fullContent); } } } } } catch (error) { console.error('Streaming error:', error); } finally { setIsStreaming(false); } }; return (
{messages.map((msg, idx) => (
{msg.role}:
{msg.content}
))} {streamingContent && (
assistant:
{streamingContent}
)}
setInput(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && sendMessage()} disabled={isStreaming} className="w-full border rounded px-3 py-2" placeholder="Type a message..." />
); } ``` ## Error Handling ### Connection Errors ```typescript theme={null} try { const response = await fetch(url, { ... }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } if (!response.body) { throw new Error('No response body'); } // Stream processing... } catch (error) { console.error('Streaming failed:', error); // Fallback to non-streaming API const fallback = await fetch('/v1/chat', { ... }); } ``` ### Timeout Handling ```typescript theme={null} const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30000); // 30s timeout try { const response = await fetch(url, { signal: controller.signal, ... }); // Process stream... } catch (error) { if (error.name === 'AbortError') { console.error('Request timed out'); } } finally { clearTimeout(timeout); } ``` ## Performance Optimization ### Chunking Strategy Perf optimizes chunk size for balance between latency and throughput: * **Small prompts**: Sends tokens individually for fastest perceived speed * **Large generations**: Batches tokens for network efficiency * **Adaptive**: Adjusts based on connection quality ### Buffering For smoother UI updates, buffer chunks: ```typescript theme={null} let buffer = ''; let lastUpdate = Date.now(); // In your streaming loop: buffer += data.chunk; const now = Date.now(); if (now - lastUpdate > 50) { // Update every 50ms setContent(prev => prev + buffer); buffer = ''; lastUpdate = now; } ``` ## Rate Limits Same limits as the Chat API: | Tier | Requests/Minute | Concurrent Streams | | ---------- | --------------- | ------------------ | | Free | 60 | 3 | | Pro | 300 | 10 | | Enterprise | Custom | Custom | ## Best Practices ### 1. Show Loading State ```typescript theme={null} {isStreaming && (
Generating response...
)} ``` ### 2. Handle Stream Interruption Allow users to stop generation: ```typescript theme={null} const abortController = new AbortController(); // Cancel button handler const handleCancel = () => { abortController.abort(); setIsStreaming(false); }; // Pass to fetch fetch(url, { signal: abortController.signal, ... }); ``` ### 3. Graceful Degradation Fall back to non-streaming if not supported: ```typescript theme={null} const supportsStreaming = 'ReadableStream' in window; if (supportsStreaming) { // Use streaming API } else { // Use regular Chat API } ``` ### 4. Optimize for Mobile Consider connection quality: ```typescript theme={null} // Detect slow connections const connection = (navigator as any).connection; const isSlowConnection = connection?.effectiveType === '2g' || connection?.effectiveType === 'slow-2g'; if (isSlowConnection) { // Use regular API or increase buffer size } ``` ## Comparison: Streaming vs Non-Streaming | Feature | Streaming | Non-Streaming | | ----------------------------- | ------------------------ | --------------------------------- | | **First token latency** | \~200ms | \~2-5s | | **Perceived speed** | Immediate | Delayed | | **Implementation complexity** | Medium | Low | | **Network efficiency** | Same | Same | | **Error recovery** | More complex | Simple | | **Best for** | Chat UIs, long responses | Batch processing, short responses | ## Related Endpoints * [Chat API](./chat) - Non-streaming version * [Metrics API](./metrics) - Analytics and monitoring * [Logs API](./logs) - Debugging and audit trails ## Support * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) * **Email**: [support@withperf.pro](mailto:support@withperf.pro) * **Examples**: [github.com/perf/examples](https://github.com/perf/examples) # Tools API Source: https://docs.withperf.pro/api-reference/tools Agentic capabilities: Web Search, Documents/RAG, and Memory # Tools API The Tools Library provides agentic capabilities for your AI applications. Add web search, document retrieval (RAG), and conversation memory with a single API. ## Available Tools | Tool | Description | Availability | | ----------------- | ----------------------------------------------- | ------------ | | **Web Search** | Real-time web search with AI-optimized results | All tiers | | **Documents/RAG** | Upload and query documents with semantic search | Pro+ | | **Memory** | Persistent conversation context across sessions | Pro+ | ## Web Search Search the web in real-time with AI-optimized results. ### Endpoint ``` POST https://api.withperf.pro/v1/tools/search ``` ### Request ```bash theme={null} curl -X POST https://api.withperf.pro/v1/tools/search \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "query": "latest developments in AI agents", "max_results": 5, "search_depth": "basic" }' ``` | Parameter | Type | Default | Description | | ----------------- | --------- | -------- | --------------------------------------- | | `query` | string | required | Search query | | `max_results` | integer | 5 | Maximum results to return (1-10) | | `search_depth` | string | "basic" | Search depth: `basic` or `advanced` | | `include_domains` | string\[] | - | Only include results from these domains | | `exclude_domains` | string\[] | - | Exclude results from these domains | ### Response ```json theme={null} { "results": [ { "title": "AI Agents Are Transforming Software Development", "url": "https://example.com/ai-agents-2024", "content": "Recent advances in AI agents have enabled autonomous coding, debugging, and deployment...", "relevance_score": 0.95 } ], "metadata": { "provider": "perf", "latency_ms": 1234, "cost_usd": 0.01 } } ``` ### Pricing | Tier | Cost per Search | | ------- | --------------- | | Starter | \$0.015 | | Pro | \$0.012 | | Growth | \$0.011 | | Team | \$0.01 | ## Documents / RAG Upload documents and query them with semantic search. Use documents as context in chat requests for Q\&A, structured extraction, and more. ### Upload Document ``` POST https://api.withperf.pro/v1/documents ``` Upload a file directly or send base64-encoded content. Perf will automatically chunk the document, generate embeddings, and make it available for search and chat context. #### File Upload (Recommended) Send the file directly using `multipart/form-data`: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/documents \ -H "Authorization: Bearer pk_live_abc123" \ -F "file=@product-datasheet.pdf" ``` **Python:** ```python theme={null} import requests response = requests.post( "https://api.withperf.pro/v1/documents", headers={"Authorization": "Bearer pk_live_abc123"}, files={"file": open("product-datasheet.pdf", "rb")} ) document_id = response.json()["id"] # Save this to use with /v1/chat ``` **Node.js:** ```javascript theme={null} const form = new FormData(); form.append("file", fs.createReadStream("product-datasheet.pdf")); const response = await fetch("https://api.withperf.pro/v1/documents", { method: "POST", headers: { "Authorization": "Bearer pk_live_abc123" }, body: form }); const { id: documentId } = await response.json(); ``` You can include optional fields as form fields alongside the file: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/documents \ -H "Authorization: Bearer pk_live_abc123" \ -F "file=@product-datasheet.pdf" \ -F "collection_id=solar-datasheets" \ -F 'metadata={"manufacturer": "Vikram Solar"}' ``` #### JSON Upload (Base64) Alternatively, send base64-encoded content in a JSON body: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/documents \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "filename": "product-datasheet.pdf", "content": "", "content_type": "application/pdf" }' ``` #### Parameters **File upload** (`multipart/form-data`): | Field | Type | Required | Description | | --------------- | ------ | -------- | --------------------------------------------- | | `file` | file | Yes | The file to upload (PDF, TXT, MD). Max 20 MB. | | `collection_id` | string | No | Collection to organize the document into | | `metadata` | string | No | JSON string of custom key-value metadata | **JSON upload** (`application/json`): | Parameter | Type | Required | Description | | --------------- | ------ | -------- | ----------------------------------------------------------------- | | `filename` | string | Yes | Original filename (used for display and format detection) | | `content` | string | Yes | Base64-encoded file content, or plain text for `.txt`/`.md` files | | `content_type` | string | No | MIME type: `application/pdf`, `text/plain`, `text/markdown` | | `collection_id` | string | No | Collection to organize the document into | | `metadata` | object | No | Custom key-value metadata for filtering | #### Response ```json theme={null} { "id": "doc_abc123", "filename": "product-datasheet.pdf", "content_type": "application/pdf", "status": "processing", "file_size_bytes": 125000, "collection_id": null, "created_at": "2024-01-15T10:30:00Z" } ``` Save the `id` from the response — you'll need it to reference this document in `/v1/chat` requests. **Document Status:** * `processing` - Being chunked and embedded * `ready` - Available for search * `failed` - Processing failed (check `error_message`) ### Use with Chat API Once a document is `ready`, pass its `id` as `document_id` in a `/v1/chat` request to use it as context: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "user", "content": "Summarize the key points from this document"} ], "document_id": "doc_abc123" }' ``` You can also combine `document_id` with `schema_id` for structured data extraction. See [Document Context in Chat API](./chat#document-context) for full details. ### Query Documents ``` POST https://api.withperf.pro/v1/documents/query ``` ```bash theme={null} curl -X POST https://api.withperf.pro/v1/documents/query \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "query": "What is the vacation policy?", "collection_id": "col_abc123", "top_k": 5, "threshold": 0.3 }' ``` | Parameter | Type | Default | Description | | --------------- | ------- | -------- | ------------------------------- | | `query` | string | required | Search query | | `collection_id` | string | none | Filter to a specific collection | | `top_k` | integer | 5 | Maximum results | | `threshold` | number | 0.3 | Minimum similarity score (0-1) | **Response:** ```json theme={null} { "query": "What is the vacation policy?", "results": [ { "chunk_id": "chunk_xyz789", "document_id": "doc_abc123", "filename": "company-handbook.pdf", "content": "Employees receive 20 days of paid vacation per year, accrued monthly...", "score": 0.94, "chunk_index": 15, "metadata": { "department": "HR" } } ], "metadata": { "latency_ms": 234, "cost_usd": 0.0001 } } ``` ### List Documents ``` GET https://api.withperf.pro/v1/documents?collection_id=col_abc123 ``` ### Delete Document ``` DELETE https://api.withperf.pro/v1/documents/{documentId} ``` ### Document Limits by Tier | Tier | Documents | Storage | Collections | | ---------- | --------- | ------- | ----------- | | Pro | 500 | 1 GB | 10 | | Growth | 5,000 | 10 GB | 50 | | Team | 50,000 | 100 GB | 500 | | Enterprise | 500,000 | 1 TB | 5,000 | ## Memory / Context Persist conversation context across sessions. The AI remembers previous interactions without you managing conversation history. ### Using Memory with Chat Add `session_id` to your chat requests to enable memory: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "user", "content": "Hi, my name is Sarah"} ], "session_id": "user_123_conversation", "memory": { "enabled": true, "include_last_n": 10 } }' ``` | Parameter | Type | Description | | ----------------------- | ------- | ----------------------------------- | | `session_id` | string | Unique session identifier | | `memory.enabled` | boolean | Enable memory for this request | | `memory.include_last_n` | integer | Include last N messages from memory | Now in a subsequent request: ```bash theme={null} curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "messages": [ {"role": "user", "content": "What is my name?"} ], "session_id": "user_123_conversation", "memory": {"enabled": true} }' ``` The AI will remember: *"Your name is Sarah, as you mentioned earlier."* ### Memory Management **List Sessions:** ``` GET https://api.withperf.pro/v1/memory ``` **Get Session:** ``` GET https://api.withperf.pro/v1/memory/{sessionId} ``` **Clear Session:** ``` POST https://api.withperf.pro/v1/memory/{sessionId}/clear ``` **Delete Session:** ``` DELETE https://api.withperf.pro/v1/memory/{sessionId} ``` ### Memory Limits by Tier | Tier | Memory Tokens | Sessions | Retention | | ---------- | ------------- | -------- | --------- | | Pro | 100,000 | 100 | 30 days | | Growth | 500,000 | 1,000 | 90 days | | Team | 2,000,000 | 10,000 | 1 year | | Enterprise | 10,000,000 | 100,000 | 2 years | ### Auto-Summarization When a session approaches its token limit, Perf automatically: 1. Summarizes older messages 2. Preserves recent messages intact 3. Continues conversation with summary + recent context This ensures conversations can continue indefinitely within your tier limits. ## Combining Tools Tools work together seamlessly. For example, search the web, store relevant information in documents, and use memory to maintain context: ```bash theme={null} # 1. Search for current information curl -X POST https://api.withperf.pro/v1/tools/search \ -H "Authorization: Bearer pk_live_abc123" \ -d '{"query": "latest AI news today"}' # 2. Chat with memory and context curl -X POST https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123" \ -d '{ "messages": [{"role": "user", "content": "Summarize the AI news I just searched"}], "session_id": "research_session", "memory": {"enabled": true} }' ``` ## Pricing Summary | Tool | Pricing Model | | ---------- | ------------------------- | | Web Search | Per search (\$0.01-0.015) | | Documents | Per embedding token | | Memory | Included in tier | ## Plan Availability | Feature | Starter | Pro | Growth | Team | Enterprise | | ------------- | ------- | --- | ------ | ---- | ---------- | | Web Search | Yes | Yes | Yes | Yes | Yes | | Documents/RAG | - | Yes | Yes | Yes | Yes | | Memory | - | Yes | Yes | Yes | Yes | ## Related * [Chat API](./chat) - Core chat endpoint with tool integration * [Streaming API](./streaming) - Real-time responses with tools * [Policies API](./policies) - Control tool usage with policies # Video Generation Source: https://docs.withperf.pro/api-reference/video Generate videos with Veo, Runway, Luma, and Pika # Video Generation API Generate videos from text prompts using cutting-edge AI models including Google's Veo 3, Runway Gen-3, Luma Dream Machine, and Pika. Video generation is **asynchronous** - you submit a job and poll for completion. ## Create Video Generation Submit a video generation job. ### Endpoint ``` POST https://api.withperf.pro/v1/video/generations ``` ### Request Body | Parameter | Type | Required | Default | Description | | ------------------ | ------ | -------- | ------------- | ------------------------------------- | | `prompt` | string | Yes | - | Description of the video to generate | | `model` | string | No | `runway-gen3` | Video generation model | | `duration_seconds` | number | No | `4` | Video duration (model-dependent max) | | `resolution` | string | No | `1080p` | Output resolution (`720p` or `1080p`) | | `aspect_ratio` | string | No | `16:9` | Video aspect ratio | ### Supported Models | Model | Provider | Max Duration | Resolution | Price | | -------------------- | --------- | ------------ | ---------- | -------------- | | `veo-3` | Google | 60s | 1080p | \$0.35/second | | `runway-gen3` | Runway | 16s | 1080p | \$0.10/second | | `luma-dream-machine` | Luma AI | 10s | 1080p | \$0.06/second | | `pika` | Pika Labs | 4s | 1080p | \$0.067/second | ### Aspect Ratios | Ratio | Description | Best For | | ------ | ----------- | ------------------------- | | `16:9` | Widescreen | YouTube, presentations | | `9:16` | Vertical | TikTok, Instagram Reels | | `1:1` | Square | Instagram, profile videos | | `4:3` | Standard | Classic video format | ### Request Examples #### cURL ```bash theme={null} curl -X POST https://api.withperf.pro/v1/video/generations \ -H "Authorization: Bearer pk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A serene mountain landscape with flowing water and dramatic clouds, cinematic quality", "model": "runway-gen3", "duration_seconds": 8, "resolution": "1080p", "aspect_ratio": "16:9" }' ``` #### JavaScript ```javascript theme={null} const response = await fetch('https://api.withperf.pro/v1/video/generations', { method: 'POST', headers: { 'Authorization': 'Bearer pk_live_abc123', 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'A serene mountain landscape with flowing water and dramatic clouds, cinematic quality', model: 'runway-gen3', duration_seconds: 8 }) }); const job = await response.json(); console.log('Job ID:', job.id); // Poll for completion using the job ID ``` #### Python ```python theme={null} import requests import time # Submit job response = requests.post( 'https://api.withperf.pro/v1/video/generations', headers={ 'Authorization': 'Bearer pk_live_abc123', 'Content-Type': 'application/json' }, json={ 'prompt': 'A serene mountain landscape with flowing water and dramatic clouds', 'model': 'runway-gen3', 'duration_seconds': 8 } ) job = response.json() job_id = job['id'] print(f'Job submitted: {job_id}') ``` ### Response (202 Accepted) ```json theme={null} { "id": "job_vid_abc123", "status": "pending", "model": "runway-gen3", "prompt": "A serene mountain landscape with flowing water and dramatic clouds, cinematic quality", "duration_seconds": 8, "resolution": "1080p", "aspect_ratio": "16:9", "estimated_cost_usd": 0.80, "perf": { "request_id": "req_vid_abc123", "model_used": "runway-gen3", "latency_ms": 234 } } ``` *** ## Check Video Generation Status Poll for the status of a video generation job. ### Endpoint ``` GET https://api.withperf.pro/v1/video/generations/:id ``` ### Path Parameters | Parameter | Type | Description | | --------- | ------ | --------------------------------------------- | | `id` | string | The job ID returned from the creation request | ### Request Example ```bash theme={null} curl -X GET https://api.withperf.pro/v1/video/generations/job_vid_abc123 \ -H "Authorization: Bearer pk_live_abc123" ``` ### Status Values | Status | Description | | ------------ | ----------------------------------------- | | `pending` | Job queued, waiting to start | | `processing` | Video is being generated | | `complete` | Video ready for download | | `failed` | Generation failed (check `error_message`) | ### Response (Processing) ```json theme={null} { "id": "job_vid_abc123", "status": "processing", "model": "runway-gen3", "prompt": "A serene mountain landscape...", "progress_percent": 45, "estimated_remaining_seconds": 30 } ``` ### Response (Complete) ```json theme={null} { "id": "job_vid_abc123", "status": "complete", "model": "runway-gen3", "prompt": "A serene mountain landscape with flowing water and dramatic clouds, cinematic quality", "video_url": "https://storage.withperf.pro/videos/job_vid_abc123.mp4", "duration_seconds": 8, "resolution": "1080p", "aspect_ratio": "16:9", "perf": { "request_id": "req_vid_abc123", "model_used": "runway-gen3", "generation_time_ms": 45234, "cost_usd": 0.80 } } ``` ### Response (Failed) ```json theme={null} { "id": "job_vid_abc123", "status": "failed", "model": "runway-gen3", "prompt": "...", "error_message": "Content policy violation: prompt contains prohibited content", "perf": { "request_id": "req_vid_abc123", "model_used": "runway-gen3" } } ``` *** ## Complete Polling Example ### JavaScript ```javascript theme={null} async function generateVideo(prompt, options = {}) { // Submit job const submitResponse = await fetch('https://api.withperf.pro/v1/video/generations', { method: 'POST', headers: { 'Authorization': 'Bearer pk_live_abc123', 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, ...options }) }); const job = await submitResponse.json(); console.log('Job submitted:', job.id); // Poll for completion while (true) { const statusResponse = await fetch( `https://api.withperf.pro/v1/video/generations/${job.id}`, { headers: { 'Authorization': 'Bearer pk_live_abc123' } } ); const status = await statusResponse.json(); console.log('Status:', status.status); if (status.status === 'complete') { return status.video_url; } if (status.status === 'failed') { throw new Error(status.error_message); } // Wait 5 seconds before next poll await new Promise(resolve => setTimeout(resolve, 5000)); } } // Usage const videoUrl = await generateVideo( 'A cat playing piano in a jazz club, cinematic lighting', { model: 'runway-gen3', duration_seconds: 8 } ); console.log('Video ready:', videoUrl); ``` ### Python ```python theme={null} import requests import time def generate_video(prompt, model='runway-gen3', duration_seconds=4): headers = {'Authorization': 'Bearer pk_live_abc123'} # Submit job response = requests.post( 'https://api.withperf.pro/v1/video/generations', headers={**headers, 'Content-Type': 'application/json'}, json={ 'prompt': prompt, 'model': model, 'duration_seconds': duration_seconds } ) job = response.json() job_id = job['id'] print(f'Job submitted: {job_id}') # Poll for completion while True: status_response = requests.get( f'https://api.withperf.pro/v1/video/generations/{job_id}', headers=headers ) status = status_response.json() print(f'Status: {status["status"]}') if status['status'] == 'complete': return status['video_url'] if status['status'] == 'failed': raise Exception(status.get('error_message', 'Video generation failed')) time.sleep(5) # Usage video_url = generate_video( 'A cat playing piano in a jazz club, cinematic lighting', model='runway-gen3', duration_seconds=8 ) print(f'Video ready: {video_url}') ``` ## Prompt Best Practices ### Be Descriptive ```json theme={null} { "prompt": "Slow motion shot of coffee being poured into a white ceramic cup, steam rising, warm morning light through window, shallow depth of field" } ``` ### Specify Camera Movement ```json theme={null} { "prompt": "Drone shot flying over a mountain range at sunrise, slowly revealing a valley with a river below, cinematic quality" } ``` ### Include Lighting and Mood ```json theme={null} { "prompt": "A lone astronaut walking on Mars surface, dramatic sunset lighting casting long shadows, dust particles in the air, sci-fi atmosphere" } ``` ## Error Responses ### 400 Bad Request ```json theme={null} { "error": { "type": "invalid_request", "message": "prompt is required" } } ``` ### 400 Duration Exceeded ```json theme={null} { "error": { "type": "invalid_request", "message": "duration_seconds exceeds maximum of 16 for runway-gen3 model" } } ``` ### 404 Job Not Found ```json theme={null} { "error": { "type": "not_found", "message": "Video generation job not found" } } ``` ## Rate Limits | Tier | Concurrent Jobs | Jobs/Day | | ---------- | --------------- | -------- | | Free | 1 | 5 | | Pro | 5 | 100 | | Enterprise | Custom | Custom | ## Related Endpoints * [Image Generation](./images) - Generate still images * [Audio Generation](./audio) - Text-to-speech * [Chat API](./chat) - Text generation # Authentication & Security Source: https://docs.withperf.pro/authentication Learn how to securely authenticate with the Perf API # Authentication & Security Learn how to securely authenticate with the Perf API and protect your integration. ## API Key Authentication Perf uses API key authentication with Bearer tokens. All requests must include your API key in the `Authorization` header. ### Header Format ``` Authorization: Bearer YOUR_API_KEY ``` ### Example Request ```bash theme={null} curl https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_live_abc123xyz..." \ -H "Content-Type: application/json" \ -d '{"messages": [{"role": "user", "content": "Hello"}]}' ``` ## API Key Types ### Test Keys (`pk_test_...`) * For development and testing * Separate usage quotas from production * No charges to your billing account * Can be regenerated freely ### Production Keys (`pk_live_...`) * For production environments * Charges applied to your billing account * Higher rate limits * Should be rotated regularly for security ## Managing API Keys ### Creating Keys 1. Log in to [withperf.pro](https://withperf.pro) 2. Navigate to **Settings** → **API Keys** 3. Click **Generate New Key** 4. Provide a descriptive name (e.g., "Production Server", "Dev Environment") 5. Select key type (test or live) 6. Copy the key immediately - it won't be shown again ### Rotating Keys We recommend rotating API keys every 90 days: 1. Generate a new key 2. Update your application configuration 3. Deploy the changes 4. Verify the new key works 5. Revoke the old key ### Revoking Keys Immediately revoke a key if: * It's been compromised * An employee with access leaves * You're retiring an application To revoke: 1. Go to **Settings** → **API Keys** 2. Find the key in the list 3. Click **Revoke** 4. Confirm the action Revoked keys are immediately invalidated. ## Security Best Practices ### 1. Never Expose Keys in Client-Side Code **❌ Don't do this:** ```javascript theme={null} // DANGER: API key exposed in browser const response = await fetch('https://api.withperf.pro/v1/chat', { headers: { 'Authorization': 'Bearer pk_live_abc123...' // Visible to users! } }); ``` **✅ Do this instead:** ```javascript theme={null} // Make requests through your backend const response = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Hello' }) }); ``` ### 2. Use Environment Variables Store API keys in environment variables, never in code: ```bash theme={null} # .env file PERF_API_KEY=pk_live_abc123xyz... ``` ```python theme={null} # Python import os api_key = os.environ.get('PERF_API_KEY') ``` ```javascript theme={null} // Node.js const apiKey = process.env.PERF_API_KEY; ``` ### 3. Restrict Key Permissions When available, use scoped keys with minimal permissions: * **Read-only keys**: For analytics dashboards * **Write-only keys**: For logging systems * **Admin keys**: For full account access (use sparingly) ### 4. Use Different Keys per Environment Maintain separate keys for: * Development * Staging * Production * CI/CD pipelines This allows you to: * Track usage by environment * Revoke specific keys without affecting others * Apply different rate limits ### 5. Monitor Key Usage Regularly review: * Request volume per key * Unusual access patterns * Failed authentication attempts * Geographic distribution Access this data in **Settings** → **API Keys** → **Usage Analytics**. ### 6. Implement Server-Side Proxies For frontend applications, create a backend proxy: ```javascript theme={null} // Backend endpoint (Node.js/Express) app.post('/api/chat', async (req, res) => { // Validate user session if (!req.session.userId) { return res.status(401).json({ error: 'Unauthorized' }); } // Apply rate limiting per user if (await isRateLimited(req.session.userId)) { return res.status(429).json({ error: 'Too many requests' }); } // Call Perf API with server-side key const response = await fetch('https://api.withperf.pro/v1/chat', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.PERF_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: req.body.messages, max_cost_per_call: 0.01 // Enforce budget }) }); const data = await response.json(); res.json(data); }); ``` ## Rate Limiting Perf enforces rate limits to ensure fair usage and system stability. ### Current Limits | Tier | Requests/Minute | Requests/Month | | ---------- | --------------- | -------------- | | Free | 60 | 1,000 | | Pro | 300 | 100,000 | | Enterprise | Custom | Custom | ### Rate Limit Headers Every response includes rate limit information: ``` X-RateLimit-Limit: 60 X-RateLimit-Remaining: 45 X-RateLimit-Reset: 1672531200 ``` ### Handling Rate Limits When you exceed limits, you'll receive a `429 Too Many Requests` response: ```json theme={null} { "error": { "type": "rate_limit_exceeded", "message": "You have exceeded your rate limit", "retry_after": 30 } } ``` Implement exponential backoff: ```python theme={null} import time import requests def call_perf_with_retry(payload, max_retries=3): for attempt in range(max_retries): response = requests.post(url, json=payload, headers=headers) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) wait_time = retry_after * (2 ** attempt) # Exponential backoff time.sleep(wait_time) continue return response.json() raise Exception("Max retries exceeded") ``` ## Data Handling * **Encryption in Transit**: TLS 1.3 for all API requests * **Prompts**: Processed in real-time, not stored permanently * **Logs**: Metadata (model used, tokens, latency) is logged for analytics ## Incident Response If you suspect a security breach: 1. **Immediately revoke** compromised API keys 2. **Review** audit logs for unauthorized access 3. **Contact** [security@withperf.pro](mailto:security@withperf.pro) 4. **Rotate** all potentially affected keys 5. **Monitor** for unusual activity ## Security Contact Report security vulnerabilities to: * **Email**: [security@withperf.pro](mailto:security@withperf.pro) * **PGP Key**: Available at [withperf.pro/security.asc](https://withperf.pro/security.asc) We have a responsible disclosure policy and provide: * Acknowledgment within 24 hours * Resolution timeframe within 30 days * Recognition in our security hall of fame ## Next Steps * [View API Reference](./api-reference/chat) * [Read Best Practices](./resources/best-practices) * [Explore Integration Examples](./integration-examples) # Frequently Asked Questions Source: https://docs.withperf.pro/faq Common questions about Perf AI Runtime Orchestrator # Frequently Asked Questions ## General ### What is Perf? Perf is an AI runtime orchestrator that sits between your application and LLM providers. We automatically select the optimal model for each request based on your cost, quality, and reliability requirements. ### How does Perf reduce costs? Perf analyzes each request and intelligently selects the most cost-effective model that can handle it. Simple queries get sent to cheaper models like GPT-4o Mini, while complex tasks use more powerful models. This typically reduces costs by 40-60% compared to using a single premium model. ### Is Perf compatible with the OpenAI API? Yes, Perf is fully OpenAI-compatible. You can replace your OpenAI base URL with Perf's endpoint and everything will work seamlessly, with added benefits of cost optimization and quality control. ## Pricing & Billing ### How does Perf pricing work? You only pay for the actual model usage. Perf charges the standard rate of whichever model we select for your request, with no markup. We make money through our enterprise plans with additional features. ### Is there a free tier? Yes, we offer a free tier with 10,000 requests per month to get started. Perfect for testing and small projects. ### Can I set cost limits? Yes, you can set cost limits at multiple levels: * Per-request maximum (`max_cost_per_call` parameter) * Daily/monthly account limits in the dashboard * Team-wide budgets for enterprise plans ## Technical ### What's the latency overhead? Perf adds minimal latency overhead (typically 20-50ms) for orchestration decisions. Our intelligent caching and model selection algorithms are optimized for speed. ### Do you support streaming? Yes, Perf fully supports streaming responses using Server-Sent Events (SSE), just like the OpenAI API. ### How do you ensure data privacy? Your prompt data is only used to process your request and is not stored permanently. All requests are proxied directly to the provider. We log metadata (model used, tokens, latency) for analytics purposes. ## Platform Features ### What analytics do you provide? Perf provides analytics through our API: * Request logs and history * Cost breakdown by model and task type * Performance metrics (latency, success rate) * Usage statistics Dashboard features are coming soon. ### Can multiple team members access the same account? Team management features are coming soon. For now, you can share API keys with your team. ### Do you offer SLA guarantees? We provide automatic failover across providers to ensure reliability. Enterprise SLA guarantees are available on request. ### Can I export my data? Yes, you can access all logs and metrics data via our API. JSON format is supported. ## Getting Started ### How long does integration take? Most teams are up and running in under 30 minutes. If you're already using OpenAI, it's as simple as changing your base URL and adding your Perf API key. ### Do you provide migration support? Yes, our team provides migration support for all paid plans. We'll help you migrate from your existing LLM setup and optimize your configuration. ### Can I test Perf before committing? Absolutely. Sign up for our free tier and test with your actual use cases. No credit card required. ### Where can I get help? * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) * **Email Support**: [support@withperf.pro](mailto:support@withperf.pro) * **Enterprise Support**: Available 24/7 for enterprise customers ## Troubleshooting ### What if a request fails? Perf includes automatic retry logic with intelligent fallback. If a request fails with one provider, we automatically retry with an alternative provider and model. ### How do I monitor quality? Perf provides quality validation including: * Response completeness checks * Format validation for extraction tasks * Automatic retry for low-quality responses You can check validation results via the API logs. *** ## Still have questions? Contact our team at [support@withperf.pro](mailto:support@withperf.pro). # Perf - AI Runtime Orchestrator for LLM Cost Optimization & Quality Control Source: https://docs.withperf.pro/index Perf is an intelligent AI infrastructure layer that automatically routes LLM requests to optimal models, generates images/audio/video, and enforces output schemas - reducing costs by up to 60% while ensuring quality. # Perf Platform Documentation Welcome to Perf - the intelligent AI runtime orchestrator that optimizes your LLM applications for cost, quality, and reliability while providing unified access to text, image, audio, and video generation. ## What is Perf? Perf is an AI infrastructure layer that sits between your application and AI providers (OpenAI, Anthropic, Google, Stability AI, Runway, and more). We provide: * **Unified API** - One API for text, images, audio, and video generation * **Intelligent Orchestration** - Automatically select the optimal model based on task, budget, and quality requirements * **Schema Enforcement** - Validate and auto-repair LLM outputs against your JSON schemas * **Continuous Learning** - Performance improves as we learn from millions of inferences ## Why Perf? ### The Problem Building production AI applications is complex: * **Fragmented APIs** - Different providers have different APIs, authentication, and response formats * **Cost Uncertainty** - Model costs vary 30x+ between providers and models * **Unreliable Outputs** - LLMs return malformed JSON, hallucinate, or refuse requests * **Manual Optimization** - Teams spend weeks tuning model selection and prompts * **Provider Lock-in** - Switching providers requires significant code changes ### The Solution Perf provides: * **One API, All Modalities** - Text, images, audio, video through OpenAI-compatible endpoints * **Intelligent Orchestration** - Automatically select the best model for each request * **Schema Enforcement** - Define JSON schemas, we validate and auto-repair outputs * **Cost Control** - Enforce budgets and automatically optimize spend * **Zero Lock-in** - Switch models/providers without code changes ## Key Features ### Multi-Modal Generation * **Text** - Chat completions with intelligent model selection * **Images** - DALL-E 3, Stable Diffusion 3, Flux, Ideogram, Imagen * **Audio** - Text-to-speech (TTS) and speech-to-text (Whisper) * **Video** - Veo 3, Runway Gen-3, Luma Dream Machine, Pika * **Voice Agents** - Real-time conversational AI agents with custom instructions, knowledge base, and content safety ### Smart Model Selection * Automatic task classification (extraction, reasoning, code, vision, audio) * Complexity-aware model selection * Per-customer preference learning * Real-time provider health monitoring ### Schema Enforcement * Define JSON schemas for structured outputs * Automatic validation and repair * Type coercion and format correction * Per-project default schemas ### Policy Enforcement * **Routing Policies** - Control model selection, set cost limits, block providers * **Content Policies** - Detect and redact PII, filter sensitive terms * **Compliance** - HIPAA-ready PII detection, audit logging * **Governance** - Policy templates for common use cases ### Tools Library * **Web Search** - Real-time web search for up-to-date information * **Documents/RAG** - Upload and query documents with semantic search * **Memory** - Persistent conversation context across sessions * **Coming Soon** - External actions (Slack, GitHub, Jira, and more) ### Cost Optimization * Per-request budget constraints * Automatic model downgrade when needed * Up to 60% cost savings vs GPT-4 * Transparent per-call billing ### Production-Ready * Automatic failover across providers * Quality validation with retry logic * Real-time dashboards and logs * Enterprise observability ## Quick Start ### 1. Get Your API Key Sign up at [dashboard.withperf.pro/sign-up](https://dashboard.withperf.pro/sign-up) and generate your API key. ### 2. Make Requests ```bash theme={null} # Text generation curl https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"messages": [{"role": "user", "content": "Hello!"}]}' # Image generation curl https://api.withperf.pro/v1/images/generations \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "A sunset over mountains", "model": "dall-e-3"}' ``` ### 3. View Analytics Monitor usage, costs, and performance in the [Dashboard](https://dashboard.withperf.pro). ## API Reference ### Text Generation * [Chat API](./api-reference/chat) - Intelligent model selection for text * [Streaming API](./api-reference/streaming) - Real-time token streaming ### Voice Agents * [Overview](./voice-agents/overview) - Build conversational voice AI agents * [JavaScript SDK](./voice-agents/sdk) - Drop-in SDK for web apps * [WebSocket Protocol](./voice-agents/websocket) - Raw protocol reference * [Python Integration](./voice-agents/python) - Server-side integration ### Media Generation * [Image Generation](./api-reference/images) - DALL-E, Stable Diffusion, Flux, and more * [Audio API](./api-reference/audio) - Text-to-speech and transcription * [Video Generation](./api-reference/video) - Veo, Runway, Luma, Pika ### Governance & Quality * [Schema Enforcement](./api-reference/schemas) - JSON schema validation and auto-repair * [Policies API](./api-reference/policies) - Routing rules, PII detection, content filtering ### Agentic Tools * [Tools API](./api-reference/tools) - Web search, documents/RAG, conversation memory ### Analytics * [Metrics API](./api-reference/metrics) - Analytics and monitoring * [Logs API](./api-reference/logs) - Debugging and audit trails ## Documentation ### Getting Started * [Quickstart Guide](./quickstart) * [Authentication](./authentication) * [Integration Examples](./integration-examples) ### Platform Guide * [Dashboard Overview](./platform/dashboard) * [Analytics & Insights](./platform/analytics) * [Team Management](./platform/team-management) ### Advanced * [Custom Orchestration Rules](./advanced/custom-routing) * [A/B Testing](./advanced/ab-testing) * [Enterprise Features](./advanced/enterprise) ## Support * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) * **Email**: [support@withperf.pro](mailto:support@withperf.pro) * **Status**: [status.withperf.pro](https://status.withperf.pro) # Integration Examples Source: https://docs.withperf.pro/integration-examples Real-world examples of integrating Perf into your application stack # Integration Examples Real-world examples of integrating Perf into your application stack. ## Table of Contents * [Next.js Integration](#nextjs-integration) * [Python FastAPI](#python-fastapi) * [Express.js Backend](#expressjs-backend) * [React Frontend (with Streaming)](#react-frontend-with-streaming) * [LangChain Integration](#langchain-integration) * [Vercel AI SDK](#vercel-ai-sdk) * [Background Jobs](#background-jobs) * [Slack Bot](#slack-bot) ## Next.js Integration ### API Route Handler ```typescript theme={null} // app/api/chat/route.ts import { NextRequest, NextResponse } from 'next/server'; const PERF_API_KEY = process.env.PERF_API_KEY!; const PERF_API_URL = 'https://api.withperf.pro/v1/chat'; export async function POST(request: NextRequest) { try { const { messages } = await request.json(); // Call Perf API const response = await fetch(PERF_API_URL, { method: 'POST', headers: { 'Authorization': `Bearer ${PERF_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ messages, max_cost_per_call: 0.01, }), }); if (!response.ok) { const error = await response.json(); return NextResponse.json( { error: error.message }, { status: response.status } ); } const data = await response.json(); return NextResponse.json(data); } catch (error) { console.error('Chat API error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } } ``` ### Client Component ```typescript theme={null} // components/ChatInterface.tsx 'use client'; import { useState } from 'react'; interface Message { role: 'user' | 'assistant'; content: string; } export default function ChatInterface() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const sendMessage = async () => { if (!input.trim()) return; const userMessage: Message = { role: 'user', content: input }; setMessages(prev => [...prev, userMessage]); setInput(''); setLoading(true); try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [...messages, userMessage], }), }); const data = await response.json(); if (data.error) { throw new Error(data.error); } // OpenAI-compatible response format const assistantMessage: Message = { role: 'assistant', content: data.choices[0].message.content, }; setMessages(prev => [...prev, assistantMessage]); } catch (error) { console.error('Error:', error); alert('Failed to send message'); } finally { setLoading(false); } }; return (
{messages.map((msg, idx) => (
{msg.content}
))} {loading &&
Thinking...
}
setInput(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && sendMessage()} className="flex-1 border rounded px-3 py-2" placeholder="Type a message..." disabled={loading} />
); } ``` ## Python FastAPI ### Main Application ```python theme={null} # main.py from fastapi import FastAPI, HTTPException, Depends from fastapi.security import HTTPBearer, HTTPAuthCredentials from pydantic import BaseModel from typing import List, Optional import httpx import os app = FastAPI() security = HTTPBearer() PERF_API_KEY = os.environ.get("PERF_API_KEY") PERF_API_URL = "https://api.withperf.pro/v1/chat" class Message(BaseModel): role: str content: str class ChatRequest(BaseModel): messages: List[Message] max_cost_per_call: Optional[float] = 0.01 class ChatResponse(BaseModel): """OpenAI-compatible response model""" id: str model: str choices: list usage: dict @app.post("/chat", response_model=ChatResponse) async def chat( request: ChatRequest, credentials: HTTPAuthCredentials = Depends(security) ): """ Chat endpoint that proxies to Perf API """ # Validate user token (your own auth logic) if not await validate_user_token(credentials.credentials): raise HTTPException(status_code=401, detail="Invalid token") # Call Perf API async with httpx.AsyncClient() as client: try: response = await client.post( PERF_API_URL, json=request.dict(), headers={ "Authorization": f"Bearer {PERF_API_KEY}", "Content-Type": "application/json" }, timeout=30.0 ) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: raise HTTPException( status_code=e.response.status_code, detail=e.response.json().get("error", "Unknown error") ) except httpx.TimeoutException: raise HTTPException(status_code=504, detail="Request timeout") async def validate_user_token(token: str) -> bool: # Implement your user authentication logic return True if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) ``` ### Background Task Processing ```python theme={null} # background_tasks.py from celery import Celery import httpx import os celery = Celery('tasks', broker='redis://localhost:6379/0') PERF_API_KEY = os.environ.get("PERF_API_KEY") @celery.task def process_document_async(document_text: str, user_id: str): """ Process documents in background using Perf """ with httpx.Client() as client: response = client.post( "https://api.withperf.pro/v1/chat", json={ "messages": [ { "role": "user", "content": f"Summarize this document:\n\n{document_text}" } ], "max_cost_per_call": 0.02 }, headers={ "Authorization": f"Bearer {PERF_API_KEY}", "Content-Type": "application/json" } ) result = response.json() # Store result in database (OpenAI-compatible format) content = result['choices'][0]['message']['content'] save_summary(user_id, content) return result ``` ## Express.js Backend ```javascript theme={null} // server.js const express = require('express'); const fetch = require('node-fetch'); require('dotenv').config(); const app = express(); app.use(express.json()); const PERF_API_KEY = process.env.PERF_API_KEY; const PERF_API_URL = 'https://api.withperf.pro/v1/chat'; // Rate limiting per user const userRateLimits = new Map(); function checkUserRateLimit(userId) { const now = Date.now(); const userLimit = userRateLimits.get(userId) || { count: 0, resetAt: now + 60000 }; if (now > userLimit.resetAt) { userLimit.count = 0; userLimit.resetAt = now + 60000; } if (userLimit.count >= 20) { return false; } userLimit.count++; userRateLimits.set(userId, userLimit); return true; } app.post('/api/chat', async (req, res) => { try { // Validate user session const userId = req.session?.userId; if (!userId) { return res.status(401).json({ error: 'Unauthorized' }); } // Check rate limit if (!checkUserRateLimit(userId)) { return res.status(429).json({ error: 'Too many requests', retryAfter: 60 }); } // Call Perf API const response = await fetch(PERF_API_URL, { method: 'POST', headers: { 'Authorization': `Bearer ${PERF_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: req.body.messages, max_cost_per_call: 0.01 }) }); const data = await response.json(); if (!response.ok) { return res.status(response.status).json(data); } // Log usage (OpenAI-compatible format) await logUsage(userId, data.usage.total_tokens); res.json(data); } catch (error) { console.error('Chat error:', error); res.status(500).json({ error: 'Internal server error' }); } }); app.listen(3000, () => { console.log('Server running on port 3000'); }); ``` ## React Frontend with Streaming ```typescript theme={null} // hooks/useChat.ts import { useState, useCallback } from 'react'; interface Message { role: 'user' | 'assistant'; content: string; } export function useChat() { const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); const [streamingContent, setStreamingContent] = useState(''); const sendMessage = useCallback(async (content: string) => { const userMessage: Message = { role: 'user', content }; setMessages(prev => [...prev, userMessage]); setIsLoading(true); setStreamingContent(''); try { const response = await fetch('/api/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [...messages, userMessage], }), }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); let fullContent = ''; while (true) { const { done, value } = await reader!.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const payload = line.slice(6); // Check for [DONE] signal (OpenAI SSE format) if (payload === '[DONE]') { setMessages(prev => [ ...prev, { role: 'assistant', content: fullContent } ]); setStreamingContent(''); break; } const data = JSON.parse(payload); const deltaContent = data.choices?.[0]?.delta?.content || ''; if (deltaContent) { fullContent += deltaContent; setStreamingContent(fullContent); } } } } } catch (error) { console.error('Streaming error:', error); alert('Failed to send message'); } finally { setIsLoading(false); } }, [messages]); return { messages, isLoading, streamingContent, sendMessage, }; } ``` ## LangChain Integration ```python theme={null} # perf_langchain.py from langchain.llms.base import LLM from typing import Any, List, Optional import httpx import os class PerfLLM(LLM): """ LangChain wrapper for Perf API """ max_cost_per_call: float = 0.01 perf_api_key: str = os.environ.get("PERF_API_KEY", "") @property def _llm_type(self) -> str: return "perf" def _call( self, prompt: str, stop: Optional[List[str]] = None ) -> str: with httpx.Client() as client: response = client.post( "https://api.withperf.pro/v1/chat", json={ "messages": [{"role": "user", "content": prompt}], "max_cost_per_call": self.max_cost_per_call }, headers={ "Authorization": f"Bearer {self.perf_api_key}", "Content-Type": "application/json" } ) response.raise_for_status() data = response.json() # OpenAI-compatible response format return data["choices"][0]["message"]["content"] # Usage from langchain.chains import LLMChain from langchain.prompts import PromptTemplate llm = PerfLLM(max_cost_per_call=0.005) template = """ Extract the key information from this text: {text} Return JSON with: name, email, phone """ prompt = PromptTemplate(template=template, input_variables=["text"]) chain = LLMChain(llm=llm, prompt=prompt) result = chain.run(text="Contact John Doe at john@example.com or 555-1234") print(result) ``` ## Vercel AI SDK ```typescript theme={null} // app/api/chat/route.ts import { OpenAIStream, StreamingTextResponse } from 'ai'; export const runtime = 'edge'; export async function POST(req: Request) { const { messages } = await req.json(); // Call Perf streaming endpoint const response = await fetch('https://api.withperf.pro/v1/chat/stream', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.PERF_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ messages }), }); // Convert OpenAI SSE format to Vercel AI SDK format const stream = new ReadableStream({ async start(controller) { const reader = response.body?.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader!.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const payload = line.slice(6); if (payload === '[DONE]') break; const data = JSON.parse(payload); const content = data.choices?.[0]?.delta?.content || ''; if (content) { controller.enqueue(new TextEncoder().encode(content)); } } } } controller.close(); }, }); return new StreamingTextResponse(stream); } ``` ## Background Jobs ```python theme={null} # batch_processor.py import asyncio import httpx from typing import List, Dict import os PERF_API_KEY = os.environ.get("PERF_API_KEY") async def process_batch(items: List[str]) -> List[Dict]: """ Process a batch of items using Perf API with concurrency control """ async with httpx.AsyncClient() as client: semaphore = asyncio.Semaphore(10) # Max 10 concurrent requests async def process_item(item: str): async with semaphore: response = await client.post( "https://api.withperf.pro/v1/chat", json={ "messages": [ {"role": "user", "content": f"Analyze: {item}"} ], "max_cost_per_call": 0.005 }, headers={ "Authorization": f"Bearer {PERF_API_KEY}", "Content-Type": "application/json" }, timeout=30.0 ) return response.json() results = await asyncio.gather( *[process_item(item) for item in items], return_exceptions=True ) return results # Usage if __name__ == "__main__": items = ["item1", "item2", "item3", ...] results = asyncio.run(process_batch(items)) # Count successful results (OpenAI-compatible format) successful = sum(1 for r in results if 'choices' in r) print(f"Processed {successful}/{len(results)} items successfully") ``` ## Slack Bot ```python theme={null} # slack_bot.py from slack_bolt import App from slack_bolt.adapter.socket_mode import SocketModeHandler import httpx import os app = App(token=os.environ.get("SLACK_BOT_TOKEN")) PERF_API_KEY = os.environ.get("PERF_API_KEY") @app.event("app_mention") def handle_mention(event, say): """ Respond to @mentions using Perf API """ user_message = event['text'] # Call Perf with httpx.Client() as client: response = client.post( "https://api.withperf.pro/v1/chat", json={ "messages": [{"role": "user", "content": user_message}], "max_cost_per_call": 0.01 }, headers={ "Authorization": f"Bearer {PERF_API_KEY}", "Content-Type": "application/json" } ) data = response.json() # OpenAI-compatible response format content = data['choices'][0]['message']['content'] model = data['model'] say( text=content, thread_ts=event['ts'], blocks=[ { "type": "section", "text": {"type": "mrkdwn", "text": content} }, { "type": "context", "elements": [ { "type": "mrkdwn", "text": f"_Model: {model}_" } ] } ] ) if __name__ == "__main__": handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]) handler.start() ``` ## Next Steps * [View full API Reference](./api-reference/chat) * [Learn about streaming](./api-reference/streaming) * [Explore best practices](./resources/best-practices) * [See supported models](./resources/models) # Alerts & Notifications Source: https://docs.withperf.pro/platform/alerts Set up alerts and notifications for your Perf account # Alerts & Notifications **Coming Soon** This feature is currently in development. Stay tuned for updates on configuring alerts and notifications for: * Cost threshold alerts * Performance degradation warnings * Provider health notifications * Custom metric alerts * Team collaboration notifications For urgent notifications, please contact [support@withperf.pro](mailto:support@withperf.pro). # null Source: https://docs.withperf.pro/platform/analytics # Analytics & Insights **Coming Soon** This feature is currently in development. You'll be able to: * Deep-dive into model performance comparisons * Analyze task-specific optimization opportunities * View cost breakdown and projections * Monitor quality and reliability metrics * Track provider health * Create custom queries and reports For early access or custom requirements, contact [sales@withperf.pro](mailto:sales@withperf.pro). # null Source: https://docs.withperf.pro/platform/dashboard # Dashboard **Coming Soon** This feature is currently in development. You'll be able to: * View real-time visibility into your LLM usage, costs, and performance * Monitor key performance indicators (total calls, latency, costs, fallback rate) * Analyze model distribution and task type breakdown * Track request volume and cost trends over time * Export reports in PDF, CSV, and JSON formats For early access or custom requirements, contact [sales@withperf.pro](mailto:sales@withperf.pro). # null Source: https://docs.withperf.pro/platform/team-management # Team Management **Coming Soon** This feature is currently in development. You'll be able to: * Invite team members with different permission levels * Control access to resources and features * Audit team activity * Manage API keys per team/project * Set up SSO and advanced authentication (Enterprise) For early access or custom requirements, contact [sales@withperf.pro](mailto:sales@withperf.pro). # Quickstart Source: https://docs.withperf.pro/quickstart Get up and running with Perf in under 5 minutes # Quickstart Guide Get up and running with Perf in under 5 minutes. ## Prerequisites * An account at [withperf.pro](https://withperf.pro) * Your API key (available in the dashboard) * Basic knowledge of REST APIs ## Step 1: Get Your API Key 1. Sign up at [dashboard.withperf.pro/sign-up](https://dashboard.withperf.pro/sign-up) 2. Navigate to **Settings** → **API Keys** 3. Click **Generate New Key** 4. Copy your key (format: `pk_live_...` for production, `pk_test_...` for testing) > **Important**: Store your API key securely. Never commit it to version control. ## Step 2: Make Your First Request ### Using cURL ```bash theme={null} curl https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_test_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "What are the three primary colors?" } ] }' ``` ### Response The response is **OpenAI-compatible**: ```json theme={null} { "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1705312200, "model": "gpt-4o-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "The three primary colors are red, blue, and yellow." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 15, "completion_tokens": 12, "total_tokens": 27 }, "perf": { "task_type": "writing", "latency_ms": 234, "fallback_used": false } } ``` ## Step 3: Add Cost Controls Control costs by setting a budget per request: ```bash theme={null} curl https://api.withperf.pro/v1/chat \ -H "Authorization: Bearer pk_test_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Write a comprehensive analysis of climate change impacts..." } ], "max_cost_per_call": 0.005 }' ``` Perf will automatically select a model that stays within your budget. If the optimal model exceeds your limit, we'll use the best alternative and include a cost warning in the response. ## Step 4: Use Streaming for Real-Time Responses For chat applications, use streaming to show responses as they're generated: ```bash theme={null} curl https://api.withperf.pro/v1/chat/stream \ -H "Authorization: Bearer pk_test_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Explain quantum computing" } ] }' ``` The response uses **OpenAI-compatible Server-Sent Events (SSE)** format: ``` data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1705312200,"model":"claude-sonnet-4-5-20250929","choices":[{"index":0,"delta":{"role":"assistant","content":"Quantum"},"finish_reason":null}]} data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1705312200,"model":"claude-sonnet-4-5-20250929","choices":[{"index":0,"delta":{"content":" computing"},"finish_reason":null}]} data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1705312200,"model":"claude-sonnet-4-5-20250929","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: [DONE] ``` ## Step 5: View Your Analytics Analytics are available via the API. Dashboard features are coming soon. Use the [Metrics API](/api-reference/metrics) to: * View total requests and costs * Analyze model distribution * Track latency metrics * Export usage data ## Language-Specific Examples ### Python ```python theme={null} import requests url = "https://api.withperf.pro/v1/chat" headers = { "Authorization": "Bearer pk_test_your_key_here", "Content-Type": "application/json" } payload = { "messages": [ {"role": "user", "content": "Hello, world!"} ], "max_cost_per_call": 0.01 } response = requests.post(url, json=payload, headers=headers) data = response.json() # OpenAI-compatible response format content = data['choices'][0]['message']['content'] model = data['model'] tokens = data['usage']['total_tokens'] print(f"Response: {content}") print(f"Model: {model}") print(f"Tokens: {tokens}") ``` ### JavaScript/TypeScript ```javascript theme={null} const response = await fetch('https://api.withperf.pro/v1/chat', { method: 'POST', headers: { 'Authorization': 'Bearer pk_test_your_key_here', 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [ { role: 'user', content: 'Hello, world!' } ], max_cost_per_call: 0.01 }) }); const data = await response.json(); // OpenAI-compatible response format const content = data.choices[0].message.content; const model = data.model; const tokens = data.usage.total_tokens; console.log('Response:', content); console.log('Model:', model); console.log('Tokens:', tokens); ``` ### Go ```go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) type Message struct { Role string `json:"role"` Content string `json:"content"` } type Request struct { Messages []Message `json:"messages"` MaxCostPerCall float64 `json:"max_cost_per_call"` } // OpenAI-compatible response structure type Response struct { Model string `json:"model"` Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` Usage struct { TotalTokens int `json:"total_tokens"` } `json:"usage"` } func main() { req := Request{ Messages: []Message{ {Role: "user", Content: "Hello, world!"}, }, MaxCostPerCall: 0.01, } jsonData, _ := json.Marshal(req) httpReq, _ := http.NewRequest("POST", "https://api.withperf.pro/v1/chat", bytes.NewBuffer(jsonData)) httpReq.Header.Set("Authorization", "Bearer pk_test_your_key_here") httpReq.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, _ := client.Do(httpReq) defer resp.Body.Close() var result Response json.NewDecoder(resp.Body).Decode(&result) fmt.Println("Response:", result.Choices[0].Message.Content) fmt.Println("Model:", result.Model) fmt.Println("Tokens:", result.Usage.TotalTokens) } ``` ## Common Use Cases ### Task-Specific Optimization Perf automatically detects your task type and selects the optimal model: ```bash theme={null} # Data Extraction - uses efficient models { "messages": [{"role": "user", "content": "Extract email and phone from: John Doe john@example.com 555-1234"}] } # Complex Reasoning - uses powerful models { "messages": [{"role": "user", "content": "Solve this logic puzzle: If all A are B..."}] } # Code Generation - uses code-specialized models { "messages": [{"role": "user", "content": "Write a binary search in Python"}] } ``` ### Multi-Turn Conversations ```json theme={null} { "messages": [ {"role": "user", "content": "What is photosynthesis?"}, {"role": "assistant", "content": "Photosynthesis is..."}, {"role": "user", "content": "How does it relate to cellular respiration?"} ] } ``` ### Structured Output Perf automatically detects extraction tasks and routes to models that excel at structured output. Simply ask for JSON in your prompt: ```json theme={null} { "messages": [ { "role": "user", "content": "Extract structured data from: 'John Smith, 35 years old, lives in NYC'\n\nReturn JSON with name, age, location" } ] } ``` ## Rate Limits * **Free Tier**: 1,000 requests/month, 60 requests/minute * **Pro Tier**: 100,000 requests/month, 300 requests/minute * **Enterprise**: Custom limits When you exceed rate limits, you'll receive a `429 Too Many Requests` response with a `Retry-After` header. ## Error Handling ```python theme={null} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() except requests.exceptions.HTTPError as e: if response.status_code == 429: print("Rate limit exceeded. Retry after:", response.headers.get('Retry-After')) elif response.status_code == 401: print("Invalid API key") elif response.status_code == 400: print("Invalid request:", response.json()) else: print("Error:", e) ``` ## Next Steps * [View full API reference](./api-reference/chat) * [Learn about authentication](./authentication) * [Explore the dashboard](./platform/dashboard) * [See integration examples](./integration-examples) * [Read best practices](./resources/best-practices) ## Need Help? * **Email**: [support@withperf.pro](mailto:support@withperf.pro) * **Documentation**: [Full docs](https://docs.withperf.pro) # Best Practices Source: https://docs.withperf.pro/resources/best-practices Best practices for using Perf AI Runtime Orchestrator # Best Practices **Coming Soon** Comprehensive best practices guide is in development, covering: ## Orchestration Strategy * When to let Perf auto-orchestrate vs. specify models * Setting cost budgets effectively * Using task type hints for better results ## Cost Optimization * Right-sizing requests and context * Batching strategies * Monitoring and optimization tips ## Quality Assurance * Setting quality thresholds * Implementing validation * Using user feedback ## Performance * Latency optimization techniques * Caching strategies * Parallel request handling ## Security * API key management * Rate limiting * Input validation ## Prompt Engineering * Writing effective prompts * Few-shot learning techniques * Using system messages For immediate guidance, contact [support@withperf.pro](mailto:support@withperf.pro). # Migration Guide Source: https://docs.withperf.pro/resources/migration Migrate from your existing LLM setup to Perf # Migration Guide **Coming Soon** Detailed migration guides are in development for: ## From OpenAI * Switching from OpenAI API to Perf * Code examples and compatibility * Migration checklist ## From Anthropic * Migrating Claude applications * Maintaining prompt compatibility * Feature mapping ## From Other Providers * Google Vertex AI migration * Azure OpenAI migration * Custom provider migration ## General Migration * Step-by-step migration process * Testing and validation * Rollback strategies * Zero-downtime migration ## Common Migration Scenarios * Next.js applications * Python FastAPI services * Express.js backends * LangChain integrations For migration assistance, contact [support@withperf.pro](mailto:support@withperf.pro) or [sales@withperf.pro](mailto:sales@withperf.pro) for enterprise support. # JavaScript SDK Source: https://docs.withperf.pro/sdks/javascript Official Perf SDK for JavaScript and TypeScript # JavaScript SDK The official Perf SDK for JavaScript and TypeScript provides a type-safe, feature-rich client for the Perf API with streaming support, automatic retries, and comprehensive error handling. ## Installation ```bash theme={null} npm install @perf_technology/sdk # or yarn add @perf_technology/sdk # or pnpm add @perf_technology/sdk ``` ## Quick Start ```typescript theme={null} import { PerfClient } from '@perf_technology/sdk'; const client = new PerfClient({ apiKey: 'pk_live_your_key_here', }); // Simple chat completion const response = await client.chat({ messages: [{ role: 'user', content: 'Hello, world!' }], maxCostPerCall: 0.01, }); console.log(response.output); console.log(`Cost: $${response.billing.costUsd}`); console.log(`Model: ${response.modelUsed}`); ``` ## Configuration ```typescript theme={null} const client = new PerfClient({ apiKey: 'pk_live_xxx', // Required: Your API key baseUrl: 'https://api.withperf.pro', // Optional: Custom base URL timeout: 30000, // Optional: Request timeout in ms (default: 30000) maxRetries: 3, // Optional: Max retry attempts (default: 3) }); ``` ## Chat Completions ### Basic Request ```typescript theme={null} const response = await client.chat({ messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'What is the capital of France?' } ], }); console.log(response.output); // "The capital of France is Paris." ``` ### With Cost Control ```typescript theme={null} const response = await client.chat({ messages: [{ role: 'user', content: 'Explain quantum computing' }], maxCostPerCall: 0.005, // Maximum 0.5 cents }); if (response.billing.costWarning) { console.log('Cost exceeded budget, cheaper model was used'); } ``` ### Request Options ```typescript theme={null} const response = await client.chat({ messages: [{ role: 'user', content: 'Generate a haiku' }], // Cost control maxCostPerCall: 0.01, // Generation parameters temperature: 0.7, maxTokens: 500, topP: 0.9, frequencyPenalty: 0.5, presencePenalty: 0.5, stop: ['\n\n'], // Output format responseFormat: 'json', // Tracking userId: 'user_123', metadata: { sessionId: 'sess_abc', feature: 'chat', }, }); ``` ## Streaming Stream responses for real-time output: ```typescript theme={null} const stream = await client.chatStream({ messages: [{ role: 'user', content: 'Write a short story' }], }); for await (const chunk of stream) { if (chunk.done) { // Final chunk with metadata console.log(`\nModel: ${chunk.modelUsed}`); console.log(`Cost: $${chunk.billing.costUsd}`); } else { // Content chunk process.stdout.write(chunk.chunk); } } ``` ### Stream to String Helper ```typescript theme={null} const content = await client.chatStreamToString({ messages: [{ role: 'user', content: 'Tell me a joke' }], }); console.log(content); ``` ## Error Handling The SDK provides typed error classes for different error scenarios: ```typescript theme={null} import { PerfClient, PerfError, AuthenticationError, RateLimitError, ValidationError, ServerError, } from '@perf_technology/sdk'; try { const response = await client.chat({ messages: [{ role: 'user', content: 'Hello' }], }); } catch (error) { if (error instanceof RateLimitError) { console.log(`Rate limited. Retry after ${error.retryAfter}s`); // Wait and retry } else if (error instanceof AuthenticationError) { console.error('Invalid API key'); // Don't retry - fix the API key } else if (error instanceof ValidationError) { console.error(`Invalid request: ${error.message}`); // Fix the request parameters } else if (error instanceof ServerError) { console.error('Server error - will auto-retry'); // SDK will automatically retry } else if (error instanceof PerfError) { console.error(`Error: ${error.code} - ${error.message}`); if (error.isRetryable) { // Safe to retry } } } ``` ### Error Properties All `PerfError` instances have these properties: | Property | Type | Description | | ------------- | ------- | ------------------------------- | | `code` | string | Machine-readable error code | | `message` | string | Human-readable description | | `status` | number | HTTP status code | | `requestId` | string | Unique request ID for debugging | | `isRetryable` | boolean | Whether safe to retry | ### Rate Limit Error ```typescript theme={null} if (error instanceof RateLimitError) { console.log(error.retryAfter); // Seconds to wait console.log(error.limit); // Rate limit ceiling console.log(error.remaining); // Remaining requests console.log(error.reset); // Unix timestamp when limit resets } ``` ## TypeScript Support The SDK is written in TypeScript and exports all types: ```typescript theme={null} import type { ChatRequest, ChatResponse, Message, StreamChunk, PerfClientOptions, } from '@perf_technology/sdk'; const request: ChatRequest = { messages: [{ role: 'user', content: 'Hello' }], maxCostPerCall: 0.01, }; ``` ## Response Types ### ChatResponse ```typescript theme={null} interface ChatResponse { modelUsed: string; output: string; billing: { costUsd: number; costWarning: boolean; }; tokens: { input: number; output: number; total: number; }; metadata: { callId: string; taskType: string; complexityScore: number; routingReason: string; latencyMs: number; fallbackUsed: boolean; validationPassed: boolean; timestamp: string; }; } ``` ### StreamChunk ```typescript theme={null} // Content chunk interface ContentChunk { chunk: string; done: false; } // Final chunk interface FinalChunk { chunk: ''; done: true; modelUsed: string; billing: { costUsd: number }; tokens: { input: number; output: number; total: number }; metadata: { /* ... */ }; } ``` ## Examples ### Multi-turn Conversation ```typescript theme={null} const messages = [ { role: 'user', content: 'What is 2+2?' }, ]; const response1 = await client.chat({ messages }); console.log(response1.output); // "4" messages.push({ role: 'assistant', content: response1.output }); messages.push({ role: 'user', content: 'Multiply that by 3' }); const response2 = await client.chat({ messages }); console.log(response2.output); // "12" ``` ### JSON Output ```typescript theme={null} const response = await client.chat({ messages: [{ role: 'user', content: 'Extract: John Smith, john@example.com, 555-1234. Return JSON with name, email, phone.' }], responseFormat: 'json', temperature: 0.2, }); const data = JSON.parse(response.output); console.log(data.name); // "John Smith" console.log(data.email); // "john@example.com" ``` ### React Hook Example ```typescript theme={null} import { useState, useCallback } from 'react'; import { PerfClient } from '@perf_technology/sdk'; const client = new PerfClient({ apiKey: process.env.PERF_API_KEY }); export function useChat() { const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); const sendMessage = useCallback(async (content: string) => { const userMessage = { role: 'user', content }; setMessages(prev => [...prev, userMessage]); setIsLoading(true); try { const response = await client.chat({ messages: [...messages, userMessage], }); setMessages(prev => [ ...prev, { role: 'assistant', content: response.output } ]); } finally { setIsLoading(false); } }, [messages]); return { messages, sendMessage, isLoading }; } ``` ## Requirements * Node.js 18.0.0 or higher * TypeScript 5.0+ (for TypeScript users) ## Support * **npm**: [@perf\_technology/sdk](https://www.npmjs.com/package/@perf_technology/sdk) * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) * **Email**: [support@withperf.pro](mailto:support@withperf.pro) # Python SDK Source: https://docs.withperf.pro/sdks/python Official Perf SDK for Python with sync and async support # Python SDK The official Perf SDK for Python provides both synchronous and asynchronous clients with Pydantic models, streaming support, and comprehensive error handling. ## Installation ```bash theme={null} pip install perf-sdk # or poetry add perf-sdk ``` ## Quick Start ```python theme={null} from perf import PerfClient client = PerfClient(api_key="pk_live_your_key_here") # Simple chat completion response = client.chat( messages=[{"role": "user", "content": "Hello, world!"}], max_cost_per_call=0.01 ) print(response.output) print(f"Cost: ${response.billing.cost_usd}") print(f"Model: {response.model_used}") ``` ## Configuration ```python theme={null} client = PerfClient( api_key="pk_live_xxx", # Required: Your API key base_url="https://api.withperf.pro", # Optional: Custom base URL timeout=30.0, # Optional: Request timeout in seconds max_retries=3 # Optional: Max retry attempts ) ``` ## Synchronous Client ### Basic Request ```python theme={null} from perf import PerfClient client = PerfClient(api_key="pk_live_xxx") response = client.chat( messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the capital of France?"} ] ) print(response.output) # "The capital of France is Paris." ``` ### With Cost Control ```python theme={null} response = client.chat( messages=[{"role": "user", "content": "Explain quantum computing"}], max_cost_per_call=0.005 # Maximum 0.5 cents ) if response.billing.cost_warning: print("Cost exceeded budget, cheaper model was used") ``` ### Request Options ```python theme={null} response = client.chat( messages=[{"role": "user", "content": "Generate a haiku"}], # Cost control max_cost_per_call=0.01, # Generation parameters temperature=0.7, max_tokens=500, top_p=0.9, frequency_penalty=0.5, presence_penalty=0.5, stop=["\n\n"], # Output format response_format="json", # Tracking user_id="user_123", metadata={ "session_id": "sess_abc", "feature": "chat" } ) ``` ## Async Client For async applications, use `AsyncPerfClient`: ```python theme={null} from perf import AsyncPerfClient import asyncio async def main(): client = AsyncPerfClient(api_key="pk_live_xxx") response = await client.chat( messages=[{"role": "user", "content": "Hello, world!"}] ) print(response.output) # Always close the client when done await client.close() asyncio.run(main()) ``` ### Context Manager ```python theme={null} async def main(): async with AsyncPerfClient(api_key="pk_live_xxx") as client: response = await client.chat( messages=[{"role": "user", "content": "Hello!"}] ) print(response.output) ``` ## Streaming ### Synchronous Streaming ```python theme={null} for chunk in client.chat_stream( messages=[{"role": "user", "content": "Write a short story"}] ): if chunk.done: # Final chunk with metadata print(f"\nModel: {chunk.model_used}") print(f"Cost: ${chunk.billing.cost_usd}") else: # Content chunk print(chunk.chunk, end="", flush=True) ``` ### Async Streaming ```python theme={null} async for chunk in await client.chat_stream( messages=[{"role": "user", "content": "Write a poem"}] ): if chunk.done: print(f"\nModel: {chunk.model_used}") else: print(chunk.chunk, end="", flush=True) ``` ## Error Handling The SDK provides typed exceptions for different error scenarios: ```python theme={null} from perf import ( PerfClient, PerfError, AuthenticationError, RateLimitError, ValidationError, ServerError ) import time client = PerfClient(api_key="pk_live_xxx") try: response = client.chat( messages=[{"role": "user", "content": "Hello"}] ) except RateLimitError as e: print(f"Rate limited. Retry after {e.retry_after}s") time.sleep(e.retry_after) # Retry the request except AuthenticationError: print("Invalid API key") # Don't retry - fix the API key except ValidationError as e: print(f"Invalid request: {e.message}") # Fix the request parameters except ServerError as e: print(f"Server error: {e.message}") # SDK will automatically retry except PerfError as e: print(f"Error: {e.code} - {e.message}") if e.is_retryable: # Safe to retry pass ``` ### Exception Properties All `PerfError` exceptions have these properties: | Property | Type | Description | | -------------- | ---- | ------------------------------- | | `code` | str | Machine-readable error code | | `message` | str | Human-readable description | | `status` | int | HTTP status code | | `request_id` | str | Unique request ID for debugging | | `is_retryable` | bool | Whether safe to retry | ### Rate Limit Error ```python theme={null} except RateLimitError as e: print(e.retry_after) # Seconds to wait print(e.limit) # Rate limit ceiling print(e.remaining) # Remaining requests print(e.reset) # Unix timestamp when limit resets ``` ## Type Hints The SDK uses Pydantic models with full type hints: ```python theme={null} from perf import PerfClient from perf.types import ChatRequest, ChatResponse, Message client = PerfClient(api_key="pk_live_xxx") messages: list[Message] = [ {"role": "user", "content": "Hello"} ] response: ChatResponse = client.chat(messages=messages) ``` ## Response Types ### ChatResponse ```python theme={null} @dataclass class ChatResponse: model_used: str output: str billing: Billing tokens: TokenUsage metadata: Metadata @dataclass class Billing: cost_usd: float cost_warning: bool @dataclass class TokenUsage: input: int output: int total: int @dataclass class Metadata: call_id: str task_type: str complexity_score: float routing_reason: str latency_ms: int fallback_used: bool validation_passed: bool timestamp: str ``` ### StreamChunk ```python theme={null} @dataclass class ContentChunk: chunk: str done: Literal[False] @dataclass class FinalChunk: chunk: str # Empty string done: Literal[True] model_used: str billing: Billing tokens: TokenUsage metadata: Metadata ``` ## Examples ### Multi-turn Conversation ```python theme={null} messages = [ {"role": "user", "content": "What is 2+2?"} ] response1 = client.chat(messages=messages) print(response1.output) # "4" messages.append({"role": "assistant", "content": response1.output}) messages.append({"role": "user", "content": "Multiply that by 3"}) response2 = client.chat(messages=messages) print(response2.output) # "12" ``` ### JSON Output ```python theme={null} import json response = client.chat( messages=[{ "role": "user", "content": "Extract: John Smith, john@example.com, 555-1234. Return JSON with name, email, phone." }], response_format="json", temperature=0.2 ) data = json.loads(response.output) print(data["name"]) # "John Smith" print(data["email"]) # "john@example.com" ``` ### FastAPI Integration ```python theme={null} from fastapi import FastAPI, HTTPException from perf import AsyncPerfClient, PerfError app = FastAPI() client = AsyncPerfClient(api_key="pk_live_xxx") @app.post("/chat") async def chat(message: str): try: response = await client.chat( messages=[{"role": "user", "content": message}], max_cost_per_call=0.01 ) return { "response": response.output, "model": response.model_used, "cost": response.billing.cost_usd } except PerfError as e: raise HTTPException(status_code=e.status, detail=e.message) @app.on_event("shutdown") async def shutdown(): await client.close() ``` ### Retry with Backoff ```python theme={null} import time import random from perf import PerfClient, PerfError client = PerfClient(api_key="pk_live_xxx") def chat_with_retry(messages, max_retries=3): for attempt in range(max_retries + 1): try: return client.chat(messages=messages) except PerfError as e: if not e.is_retryable or attempt == max_retries: raise # Exponential backoff with jitter delay = (2 ** attempt) + random.random() print(f"Retrying in {delay:.1f}s...") time.sleep(delay) # Usage response = chat_with_retry([{"role": "user", "content": "Hello"}]) ``` ## Requirements * Python 3.9 or higher * Dependencies: `httpx`, `pydantic` ## Support * **PyPI**: [perf-sdk](https://pypi.org/project/perf-sdk/) * **Documentation**: [docs.withperf.pro](https://docs.withperf.pro) * **Email**: [support@withperf.pro](mailto:support@withperf.pro) # Voice Agents Source: https://docs.withperf.pro/voice-agents/overview Build conversational voice AI agents with Perf # Voice Agents Build real-time conversational voice agents powered by your custom instructions, knowledge base, and content safety policies. Voice agents handle speech recognition, natural language understanding, response generation, and text-to-speech — all through a single WebSocket connection. ## How It Works ``` Your App → WebSocket → Perf → LLM + TTS + STT ↕ Audio streaming (PCM16, 16kHz, mono) ``` 1. Your application opens a WebSocket connection to Perf 2. Perf establishes a real-time voice pipeline (speech-to-text, LLM, text-to-speech) 3. Your app streams microphone audio to Perf, and receives agent audio + transcripts back 4. Content safety policies are evaluated on every turn ## Quick Start ### 1. Create a Voice Agent In the [Perf Dashboard](https://dashboard.withperf.pro/dashboard/voice/agents), click **Create Agent** and configure: * **Name** — A label for your agent (e.g. "Customer Support") * **System Prompt** — Instructions that define the agent's behavior * **Voice** — Choose from available voices * **First Message** — What the agent says when a conversation starts * **Content Policy** (optional) — Attach a policy for PII redaction, blocked terms, or custom safety criteria ### 2. Add the SDK The fastest way to integrate is the [PerfVoice JavaScript SDK](./sdk): ```html theme={null} ``` That's it. The SDK handles microphone capture, audio encoding, WebSocket protocol, audio playback, interruptions, and ping/pong keepalive. ### 3. Test It Click your start button, allow microphone access, and speak. You should hear the agent respond and see transcripts in the console. ## Features | Feature | Description | | ------------------------- | -------------------------------------------------------- | | **Real-time streaming** | Sub-second latency from speech to agent response | | **Interruption handling** | Users can interrupt the agent mid-sentence | | **Custom voices** | Choose from multiple voice options | | **Knowledge base (RAG)** | Attach document collections for grounded answers | | **Web search** | Enable real-time web search for up-to-date information | | **Content safety** | PII detection, blocked terms, custom criteria | | **Loop detection** | Automatic detection and breaking of conversational loops | | **Transcripts** | Real-time agent and user transcripts via events | ## Integration Options | Method | Best For | Docs | | ------------------ | ------------------------------------ | --------------------------------- | | **JavaScript SDK** | Web apps, fastest integration | [SDK Reference](./sdk) | | **Raw WebSocket** | Full control, custom audio pipelines | [WebSocket Protocol](./websocket) | | **Python** | Server-side, IVR systems, telephony | [Python Integration](./python) | ## Authentication Voice agent connections require two parameters: | Parameter | Description | | ---------- | ---------------------------------------------- | | `api_key` | Your project API key (format: `pk_live_...`) | | `agent_id` | The voice agent ID (from the dashboard or API) | These are passed as query parameters on the WebSocket URL: ``` wss://api.withperf.pro/v1/voice/conversation?api_key=YOUR_API_KEY&agent_id=YOUR_AGENT_ID ``` ## Audio Format All audio is streamed as **PCM 16-bit, 16kHz, mono, little-endian**: | Property | Value | | ----------- | ------------------------------- | | Encoding | PCM signed 16-bit integer | | Sample rate | 16,000 Hz | | Channels | 1 (mono) | | Byte order | Little-endian | | Transport | Base64-encoded in JSON messages | ## Content Safety Voice agents support the same content safety policies as the rest of the Perf platform: * **Blocked terms** — Prevent specific words or phrases in agent responses * **PII detection** — Detect and redact personally identifiable information * **Custom criteria** — Define LLM-evaluated safety rules (e.g. "Agent must not provide medical advice") * **Filler phrases** — Play natural filler audio while safety evaluation runs Configure policies in the [Dashboard](https://dashboard.withperf.pro/dashboard/policies) and attach them to your voice agent. ## Next Steps * [JavaScript SDK Reference](./sdk) — Full SDK API documentation * [WebSocket Protocol](./websocket) — Raw WebSocket integration for advanced use cases * [Python Integration](./python) — Server-side Python integration * [Content Policies](../api-reference/policies) — Configure safety policies # Python Integration Source: https://docs.withperf.pro/voice-agents/python Server-side Python integration for Perf Voice Agents # Python Integration Connect to Perf Voice Agents from Python for server-side applications, IVR systems, telephony integrations, or testing. ## Prerequisites ```bash theme={null} pip install websocket-client pyaudio ``` * **websocket-client** — WebSocket client library * **PyAudio** — Cross-platform audio I/O (requires PortAudio system library) ### Installing PortAudio PyAudio requires the PortAudio system library: ```bash theme={null} # macOS brew install portaudio # Ubuntu/Debian sudo apt-get install portaudio19-dev # Windows (via pip) pip install pyaudio # includes prebuilt binaries ``` ## Quick Start ```python theme={null} import websocket import json import base64 import threading import pyaudio WS_URL = 'wss://api.withperf.pro/v1/voice/conversation' API_KEY = 'YOUR_API_KEY' AGENT_ID = 'YOUR_AGENT_ID' RATE = 16000 CHUNK = 2048 ready = False # Audio setup pa = pyaudio.PyAudio() mic_stream = pa.open(format=pyaudio.paInt16, channels=1, rate=RATE, input=True, frames_per_buffer=CHUNK) spk_stream = pa.open(format=pyaudio.paInt16, channels=1, rate=RATE, output=True, frames_per_buffer=CHUNK) def on_message(ws, message): global ready data = json.loads(message) msg_type = data.get('type', '') if msg_type == 'conversation_initiation_metadata': ready = True conv_id = data.get('conversation_initiation_metadata_event', {}).get('conversation_id') print(f'Session started: {conv_id}') elif msg_type == 'audio': audio_b64 = data.get('audio_event', {}).get('audio_base_64', '') if audio_b64: spk_stream.write(base64.b64decode(audio_b64)) elif msg_type == 'agent_response': text = data.get('agent_response_event', {}).get('agent_response', '') if text: print(f'Agent: {text}') elif msg_type == 'user_transcript': text = data.get('user_transcription_event', {}).get('user_transcript', '') if text: print(f'You: {text}') elif msg_type == 'ping': event_id = data.get('ping_event', {}).get('event_id') ws.send(json.dumps({'type': 'pong', 'event_id': event_id})) def send_audio(ws): """Stream microphone audio as base64 PCM16 chunks.""" while ws.sock and ws.sock.connected: if not ready: continue pcm_data = mic_stream.read(CHUNK, exception_on_overflow=False) b64 = base64.b64encode(pcm_data).decode('utf-8') ws.send(json.dumps({'user_audio_chunk': b64})) def on_open(ws): threading.Thread(target=send_audio, args=(ws,), daemon=True).start() print('Connected — speak now') def on_close(ws, code, reason): print(f'Disconnected (code={code}, reason={reason})') def on_error(ws, error): print(f'Error: {error}') url = f'{WS_URL}?api_key={API_KEY}&agent_id={AGENT_ID}' ws = websocket.WebSocketApp(url, on_message=on_message, on_open=on_open, on_close=on_close, on_error=on_error) ws.run_forever() ``` ## How It Works 1. **Connect** — Opens a WebSocket to `wss://api.withperf.pro/v1/voice/conversation` with your API key and agent ID 2. **Wait for init** — The `conversation_initiation_metadata` message signals the pipeline is ready 3. **Stream audio** — A background thread reads microphone PCM16 chunks, base64-encodes them, and sends via WebSocket 4. **Play responses** — Agent audio arrives as base64 PCM16 and is written directly to the speaker stream 5. **Keepalive** — Responds to `ping` messages with `pong` to keep the connection alive ## Audio Format | Property | Value | | ----------- | --------------------------------------------- | | Encoding | PCM signed 16-bit integer (`pyaudio.paInt16`) | | Sample rate | 16,000 Hz | | Channels | 1 (mono) | | Chunk size | 2048 samples | | Transport | Base64-encoded JSON messages | ## Sending Pre-Recorded Audio To send a WAV file instead of live microphone input: ```python theme={null} import wave def send_wav_file(ws, filepath): """Send a WAV file as audio chunks.""" with wave.open(filepath, 'rb') as wf: assert wf.getsampwidth() == 2, 'Must be 16-bit PCM' assert wf.getnchannels() == 1, 'Must be mono' assert wf.getframerate() == 16000, 'Must be 16kHz' while True: frames = wf.readframes(2048) if not frames: break b64 = base64.b64encode(frames).decode('utf-8') ws.send(json.dumps({'user_audio_chunk': b64})) # Pace to real-time (2048 samples at 16kHz = 128ms) time.sleep(0.128) ``` ## Saving Transcripts ```python theme={null} transcripts = [] def on_message(ws, message): global ready data = json.loads(message) msg_type = data.get('type', '') if msg_type == 'agent_response': text = data.get('agent_response_event', {}).get('agent_response', '') if text: transcripts.append({'role': 'agent', 'text': text}) elif msg_type == 'user_transcript': text = data.get('user_transcription_event', {}).get('user_transcript', '') if text: transcripts.append({'role': 'user', 'text': text}) # ... handle other message types # After conversation ends: for t in transcripts: print(f"{t['role'].capitalize()}: {t['text']}") ``` ## Connection Test Quick test without audio — verify authentication and agent connectivity: ```python theme={null} import websocket import json url = 'wss://api.withperf.pro/v1/voice/conversation?api_key=YOUR_API_KEY&agent_id=YOUR_AGENT_ID' ws = websocket.create_connection(url) response = ws.recv() data = json.loads(response) if data.get('type') == 'conversation_initiation_metadata': conv_id = data['conversation_initiation_metadata_event']['conversation_id'] print(f'Connected successfully. Session: {conv_id}') else: print(f'Unexpected response: {data}') ws.close() ``` ## Error Handling | Error | Cause | Resolution | | --------------------------- | -------------------------------------- | -------------------------------------------- | | `ConnectionRefused` | Invalid URL or server down | Verify the WebSocket URL | | WebSocket close code `4001` | Invalid API key | Check your `api_key` parameter | | WebSocket close code `4004` | Invalid agent ID | Verify the `agent_id` exists in your project | | WebSocket close code `1008` | Sent audio before init | Wait for `conversation_initiation_metadata` | | `OSError: [Errno -9999]` | No audio device available | Check microphone is connected and accessible | | Audio crackling/gaps | Chunk size too small or CPU overloaded | Increase `CHUNK` size or reduce processing | ## Related * [Voice Agents Overview](./overview) — Architecture and features * [JavaScript SDK](./sdk) — For web applications * [WebSocket Protocol](./websocket) — Full protocol reference # JavaScript SDK Source: https://docs.withperf.pro/voice-agents/sdk PerfVoice SDK reference — drop-in voice agent integration for web apps # JavaScript SDK The PerfVoice SDK is a single JavaScript file that handles the entire voice agent integration — WebSocket connection, microphone capture, audio encoding, playback, interruptions, and keepalive. No dependencies, no build step. ## Installation Add the SDK via a script tag: ```html theme={null} ``` Or install via npm (coming soon): ```bash theme={null} npm install @perf-ai/voice ``` The SDK exports a `PerfVoice` class globally (or as a UMD/CommonJS module). ## Quick Start ```html theme={null} ``` ## Constructor ```javascript theme={null} const voice = new PerfVoice(options); ``` | Parameter | Type | Required | Default | Description | | --------- | ------ | -------- | ---------------------------------------------- | ------------------------------------ | | `apiKey` | string | Yes | — | Your project API key (`pk_live_...`) | | `agentId` | string | Yes | — | Voice agent ID from the dashboard | | `wsUrl` | string | No | `wss://api.withperf.pro/v1/voice/conversation` | WebSocket endpoint URL | ## Methods ### `voice.start()` Start a voice conversation. Requests microphone permission, opens a WebSocket connection, and begins streaming audio. ```javascript theme={null} voice.start() .then(() => console.log('Conversation started')) .catch((err) => console.error('Failed to start:', err)); ``` Returns a `Promise` that resolves when the connection is established and the agent is ready. Rejects if microphone access is denied or the WebSocket connection fails. > **Note:** Browsers require a user gesture (click/tap) before allowing microphone access. Always call `start()` from a button click handler. ### `voice.stop()` End the conversation immediately. Stops all audio playback, releases the microphone, and closes the WebSocket. ```javascript theme={null} voice.stop(); ``` This method is synchronous and safe to call at any time, even if the conversation hasn't started. ## Events Subscribe to events with `voice.on(event, callback)` and unsubscribe with `voice.off(event, callback)`. ### `transcript` Fired when a transcript is available for either the agent or the user. ```javascript theme={null} voice.on('transcript', (role, text) => { // role: 'agent' or 'user' console.log(role + ': ' + text); }); ``` | Argument | Type | Description | | -------- | --------------------- | ------------------- | | `role` | `'agent'` \| `'user'` | Who said it | | `text` | string | The transcript text | ### `connected` Fired when the voice session is established and the agent is ready. ```javascript theme={null} voice.on('connected', (conversationId) => { console.log('Session:', conversationId); }); ``` | Argument | Type | Description | | ---------------- | ------ | ------------------------- | | `conversationId` | string | Unique session identifier | ### `disconnected` Fired when the WebSocket connection closes (either by calling `stop()` or due to a server-side close). ```javascript theme={null} voice.on('disconnected', (code, reason) => { console.log('Disconnected:', code, reason); }); ``` | Argument | Type | Description | | -------- | ------ | --------------------------- | | `code` | number | WebSocket close code | | `reason` | string | Close reason (may be empty) | ### `status` Fired whenever the connection status changes. ```javascript theme={null} voice.on('status', (status) => { // status: 'disconnected', 'connecting', or 'connected' updateUI(status); }); ``` | Argument | Type | Description | | -------- | --------------------------------------------------- | ------------- | | `status` | `'disconnected'` \| `'connecting'` \| `'connected'` | Current state | ### `error` Fired when an error occurs (microphone denied, WebSocket failure, etc.). ```javascript theme={null} voice.on('error', (err) => { console.error('Voice error:', err.message); }); ``` | Argument | Type | Description | | -------- | ----- | ---------------- | | `err` | Error | The error object | ### `interruption` Fired when the user interrupts the agent (starts speaking while the agent is talking). The SDK automatically stops agent audio playback. ```javascript theme={null} voice.on('interruption', () => { console.log('User interrupted the agent'); }); ``` ### `message` Fired for any unhandled message type from the server. Useful for debugging. ```javascript theme={null} voice.on('message', (data) => { console.log('Raw message:', data); }); ``` ## Properties | Property | Type | Description | | ---------------------- | -------------- | ------------------------------------------------------------------ | | `voice.status` | string | Current status: `'disconnected'`, `'connecting'`, or `'connected'` | | `voice.conversationId` | string \| null | Current session ID (null when disconnected) | ## Full Example A complete example with status indicator, transcript display, and error handling: ```html theme={null} Voice Agent Disconnected

``` ## React Integration ```jsx theme={null} import { useEffect, useRef, useState } from 'react'; function VoiceAgent({ apiKey, agentId }) { const voiceRef = useRef(null); const [status, setStatus] = useState('disconnected'); const [messages, setMessages] = useState([]); useEffect(() => { const voice = new PerfVoice({ apiKey, agentId }); voiceRef.current = voice; voice.on('status', setStatus); voice.on('transcript', (role, text) => { setMessages((prev) => [...prev, { role, text }]); }); voice.on('error', (err) => console.error(err)); return () => voice.stop(); }, [apiKey, agentId]); return (

Status: {status}

{messages.map((m, i) => (

{m.role}: {m.text}

))}
); } ``` ## Error Handling | Error | Cause | Resolution | | ------------------------------------------ | ------------------------------------- | --------------------------------------------------- | | `NotAllowedError` | Microphone permission denied | Prompt user to allow microphone in browser settings | | `WebSocket connection failed` | Network issue or invalid credentials | Check API key and network connectivity | | `Already connecting` / `Already connected` | `start()` called while already active | Check `voice.status` before calling `start()` | ## Browser Support The SDK requires: * `navigator.mediaDevices.getUserMedia` (microphone access) * `AudioContext` (audio playback) * `WebSocket` (real-time communication) Supported in all modern browsers: Chrome 60+, Firefox 55+, Safari 14+, Edge 79+. ## Related * [Voice Agents Overview](./overview) — Architecture and features * [WebSocket Protocol](./websocket) — Raw WebSocket integration * [Python Integration](./python) — Server-side Python integration # WebSocket Protocol Source: https://docs.withperf.pro/voice-agents/websocket Raw WebSocket integration for voice agents — full protocol reference # WebSocket Protocol This page documents the raw WebSocket protocol for Perf Voice Agents. Use this if you need full control over the audio pipeline or are integrating from a platform where the [JavaScript SDK](./sdk) isn't available. > For most web applications, the [JavaScript SDK](./sdk) is the recommended approach — it handles all of the protocol details described here. ## Connection ### Endpoint ``` wss://api.withperf.pro/v1/voice/conversation ``` ### Query Parameters | Parameter | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------ | | `api_key` | string | Yes | Your project API key (`pk_live_...`) | | `agent_id` | string | Yes | Voice agent ID | ### Example ```javascript theme={null} const ws = new WebSocket( 'wss://api.withperf.pro/v1/voice/conversation?api_key=YOUR_API_KEY&agent_id=YOUR_AGENT_ID' ); ``` ## Protocol Flow ``` Client Perf | | |------- WebSocket OPEN ------->| | |--- connects to voice pipeline |<-- conversation_initiation ---| | | |--- user_audio_chunk --------->| (repeat: stream mic audio) |<-- audio ---------------------| (agent speaks back) |<-- agent_response ------------| (agent transcript) |<-- user_transcript -----------| (user transcript) | | |<-- ping ----------------------| (keepalive) |--- pong --------------------->| | | |<-- interruption --------------| (user spoke over agent) | | |------- WebSocket CLOSE ------>| ``` > **Important:** Do not send audio until you receive the `conversation_initiation_metadata` message. Sending audio before initialization will cause the connection to close with code `1008`. ## Messages: Client → Server ### Send Audio Stream microphone audio as base64-encoded PCM16 chunks: ```json theme={null} { "user_audio_chunk": "" } ``` **Audio format:** PCM 16-bit signed integer, 16kHz, mono, little-endian. Base64-encode the raw bytes. **Recommended chunk size:** 2048 samples (128ms at 16kHz). #### JavaScript Example: Capture and Send Microphone Audio ```javascript theme={null} const stream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true, noiseSuppression: true } }); const audioCtx = new AudioContext({ sampleRate: 16000 }); const source = audioCtx.createMediaStreamSource(stream); const processor = audioCtx.createScriptProcessor(2048, 1, 1); processor.onaudioprocess = (e) => { if (ws.readyState !== WebSocket.OPEN || !ready) return; const input = e.inputBuffer.getChannelData(0); const pcm16 = new Int16Array(input.length); for (let i = 0; i < input.length; i++) { const s = Math.max(-1, Math.min(1, input[i])); pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF; } const bytes = new Uint8Array(pcm16.buffer); let binary = ''; for (let j = 0; j < bytes.length; j++) { binary += String.fromCharCode(bytes[j]); } ws.send(JSON.stringify({ user_audio_chunk: btoa(binary) })); }; source.connect(processor); processor.connect(audioCtx.destination); ``` ### Pong (Keepalive Response) Reply to `ping` messages to keep the connection alive: ```json theme={null} { "type": "pong", "event_id": "" } ``` ## Messages: Server → Client ### `conversation_initiation_metadata` Sent once after connection is established. Signals that the voice pipeline is ready. ```json theme={null} { "type": "conversation_initiation_metadata", "conversation_initiation_metadata_event": { "conversation_id": "conv_abc123", "agent_output_audio_format": "pcm_16000" } } ``` | Field | Description | | --------------------------- | ----------------------------------------- | | `conversation_id` | Unique session identifier | | `agent_output_audio_format` | Output audio format (usually `pcm_16000`) | **Start sending audio only after receiving this message.** ### `audio` Agent speech audio. Base64-encoded PCM16, same format as input. ```json theme={null} { "type": "audio", "audio_event": { "audio_base_64": "" } } ``` #### JavaScript Example: Play Agent Audio ```javascript theme={null} let nextPlayTime = 0; const sources = []; function playAudio(base64) { const bin = atob(base64); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); const pcm16 = new Int16Array(bytes.buffer); const float32 = new Float32Array(pcm16.length); for (let i = 0; i < pcm16.length; i++) float32[i] = pcm16[i] / 32768; const buffer = audioCtx.createBuffer(1, float32.length, 16000); buffer.getChannelData(0).set(float32); const src = audioCtx.createBufferSource(); src.buffer = buffer; src.connect(audioCtx.destination); // Schedule sequentially to avoid gaps const now = audioCtx.currentTime; if (nextPlayTime < now) nextPlayTime = now; src.start(nextPlayTime); nextPlayTime += buffer.duration; // Track for interruption cleanup sources.push(src); src.onended = () => { const idx = sources.indexOf(src); if (idx !== -1) sources.splice(idx, 1); }; } ``` ### `agent_response` The agent's text response (transcript of what the agent is saying). ```json theme={null} { "type": "agent_response", "agent_response_event": { "agent_response": "Hello! How can I help you today?" } } ``` ### `user_transcript` Transcript of what the user said. ```json theme={null} { "type": "user_transcript", "user_transcription_event": { "user_transcript": "I'd like to check on my order status." } } ``` ### `interruption` Sent when the user speaks while the agent is talking. **You must stop all currently playing agent audio immediately** to avoid the agent's voice overlapping with the new response. ```json theme={null} { "type": "interruption" } ``` ```javascript theme={null} // Handle interruption — stop all playing audio sources.forEach(s => { try { s.stop(); } catch (e) {} }); sources.length = 0; nextPlayTime = 0; ``` ### `ping` Keepalive ping. You must respond with a `pong` to keep the connection alive. ```json theme={null} { "type": "ping", "ping_event": { "event_id": 12345 } } ``` ## Complete JavaScript Example A full working implementation using raw WebSocket (no SDK): ```javascript theme={null} const WS_URL = 'wss://api.withperf.pro/v1/voice/conversation'; const API_KEY = 'YOUR_API_KEY'; const AGENT_ID = 'YOUR_AGENT_ID'; let ws, audioCtx, micStream, ready = false, nextPlay = 0, sources = []; async function startVoice() { // 1. Get microphone micStream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true, noiseSuppression: true } }); audioCtx = new AudioContext({ sampleRate: 16000 }); const mic = audioCtx.createMediaStreamSource(micStream); const proc = audioCtx.createScriptProcessor(2048, 1, 1); // 2. Stream mic audio as base64 PCM16 proc.onaudioprocess = (e) => { if (!ws || ws.readyState !== WebSocket.OPEN || !ready) return; const input = e.inputBuffer.getChannelData(0); const pcm = new Int16Array(input.length); for (let i = 0; i < input.length; i++) { const s = Math.max(-1, Math.min(1, input[i])); pcm[i] = s < 0 ? s * 0x8000 : s * 0x7FFF; } const bytes = new Uint8Array(pcm.buffer); let bin = ''; for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); ws.send(JSON.stringify({ user_audio_chunk: btoa(bin) })); }; mic.connect(proc); proc.connect(audioCtx.destination); // 3. Connect WebSocket ws = new WebSocket(WS_URL + '?api_key=' + API_KEY + '&agent_id=' + AGENT_ID); ws.onmessage = (event) => { if (typeof event.data !== 'string') return; const data = JSON.parse(event.data); switch (data.type) { case 'conversation_initiation_metadata': ready = true; console.log('Session:', data.conversation_initiation_metadata_event?.conversation_id); break; case 'audio': if (data.audio_event?.audio_base_64) playAudio(data.audio_event.audio_base_64); break; case 'agent_response': console.log('Agent:', data.agent_response_event?.agent_response); break; case 'user_transcript': console.log('You:', data.user_transcription_event?.user_transcript); break; case 'interruption': sources.forEach(s => { try { s.stop(); } catch (e) {} }); sources = []; nextPlay = 0; break; case 'ping': ws.send(JSON.stringify({ type: 'pong', event_id: data.ping_event?.event_id })); break; } }; ws.onclose = () => stopVoice(); } // 4. Play agent audio (base64 PCM16 → AudioBuffer) function playAudio(base64) { if (!audioCtx) return; const bin = atob(base64), bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); const pcm = new Int16Array(bytes.buffer); const f32 = new Float32Array(pcm.length); for (let i = 0; i < pcm.length; i++) f32[i] = pcm[i] / 32768; const buf = audioCtx.createBuffer(1, f32.length, 16000); buf.getChannelData(0).set(f32); const src = audioCtx.createBufferSource(); src.buffer = buf; src.connect(audioCtx.destination); const now = audioCtx.currentTime; if (nextPlay < now) nextPlay = now; src.start(nextPlay); nextPlay += buf.duration; sources.push(src); src.onended = () => { const i = sources.indexOf(src); if (i !== -1) sources.splice(i, 1); }; } // 5. Cleanup function stopVoice() { ready = false; sources.forEach(s => { try { s.stop(); } catch (e) {} }); sources = []; nextPlay = 0; if (micStream) { micStream.getTracks().forEach(t => t.stop()); micStream = null; } if (audioCtx) { audioCtx.close().catch(() => {}); audioCtx = null; } if (ws) { ws.close(); ws = null; } } ``` ## WebSocket Close Codes | Code | Meaning | | ------ | ------------------------------------------------------------ | | `1000` | Normal closure (client or server initiated) | | `1008` | Policy violation (e.g., sending audio before initialization) | | `1011` | Server error (internal pipeline failure) | | `4001` | Authentication failed (invalid API key) | | `4004` | Agent not found (invalid agent\_id) | ## Troubleshooting | Symptom | Cause | Fix | | ----------------------------------- | -------------------------------------------------------- | --------------------------------------------------------- | | Immediate disconnect with code 1008 | Sending audio before `conversation_initiation_metadata` | Wait for the init message before streaming audio | | No audio from agent | Playing audio as binary instead of decoding base64 PCM16 | Decode base64 → Int16Array → Float32Array → AudioBuffer | | Agent speaks over itself | Not handling `interruption` events | Stop all scheduled AudioBufferSourceNodes on interruption | | Connection drops after \~30s | Not responding to `ping` messages | Send `pong` response with the `event_id` from each ping | | Audio is garbled | Wrong sample rate or encoding | Ensure PCM16, 16kHz, mono, little-endian | ## Related * [Voice Agents Overview](./overview) — Architecture and features * [JavaScript SDK](./sdk) — Recommended for web apps * [Python Integration](./python) — Server-side integration