# App tracking for AI requests Source: https://docs.orq.ai/docs/ai-gateway/app-tracking Track LLM usage by application context to segment analytics, costs, and performance. **Use Cases** * Attributing token costs and latency to specific features or services. * Monitoring which internal tools or products drive the most LLM usage. * Filtering observability dashboards by application for debugging or billing. * Enforcing separate budgets per product line or team. For the full set of ways to attach context to a request, including identity, threads, and custom metadata, see [Request Metadata](/docs/ai-gateway/request-metadata). *** ## Quick Start Name your requests to track usage by application or service. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Write a professional email", "name": "EmailAssistant-Production" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Write a professional email", name: "EmailAssistant-Production", }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Write a professional email", extra_body={"name": "EmailAssistant-Production"}, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Write a professional email" }], name: "EmailAssistant-Production", }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[{"role": "user", "content": "Write a professional email"}], extra_body={"name": "EmailAssistant-Production"}, ) ``` ## Configuration | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------------------------------------------------------------------------------------- | | `name` | string | No | The name to display on the trace. If not specified, the default system name will be used. | For backwards compatibility, `orq.name` is also supported but deprecated. Use top-level `name` for new implementations. **Default behavior**: If no name provided, system uses default identifier. ## Naming Conventions ### Recommended patterns ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Service-Environment "UserAPI-Production"; "ChatBot-Development"; // Team-Service-Feature "Platform-Auth-OAuth"; "ML-Recommendations-v2"; // Application-Version "MobileApp-v3-1"; "WebPortal-v2-0"; ``` ### Best practices * Use consistent patterns across team. * Include environment (dev/staging/prod). * Avoid timestamps or dynamic values. * Keep names under 50 characters. * Use alphanumeric and hyphens only. ## Use Cases | Scenario | Naming Strategy | Example | | ----------------- | ---------------------- | --------------------------------- | | **Microservices** | Service-based naming | `user-service`, `payment-api` | | **Multi-tenant** | Tenant identification | `tenant-123`, `enterprise-client` | | **A/B testing** | Variant tracking | `experiment-A`, `control-group` | | **Feature flags** | Feature identification | `new-ui-beta`, `legacy-flow` | ## Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Help me write a professional email to follow up on a job interview", "name": "ContentGenerator-BlogPosts" }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Help me write a professional email to follow up on a job interview" } ], "name": "ContentGenerator-BlogPosts" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Help me write a professional email to follow up on a job interview", name: "ContentGenerator-BlogPosts", }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Help me write a professional email to follow up on a job interview", extra_body={"name": "ContentGenerator-BlogPosts"}, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [ { role: "user", content: "Help me write a professional email to follow up on a job interview", }, ], name: "ContentGenerator-BlogPosts", }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Help me write a professional email to follow up on a job interview", } ], extra_body={"name": "ContentGenerator-BlogPosts"}, ) ``` ## Environment Management ### Configuration by environment ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const getTrackingName = (service, environment) => { return `${service}-${environment}`; }; // Usage const trackingConfig = { name: getTrackingName("ChatBot", process.env.NODE_ENV), // Results in: "ChatBot-development", "ChatBot-production" }; ``` ### Environment-specific examples ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Development name: "UserAPI-Dev" // Staging name: "UserAPI-Staging" // Production name: "UserAPI-Prod" ``` ## Usage ### Filtering by application * View requests by specific app/service. * Compare performance across applications. * Track costs per application. * Monitor error rates by service. ### Metrics available The following metrics are available for App Tracking: * Request volume per application. * Response times by service. * Cost allocation by project. * Error patterns by environment. ## Advanced Patterns ### Dynamic naming ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const generateTrackingName = (userId, feature) => { // For multi-tenant scenarios return `tenant-${userId}-${feature}`; }; // Usage name: generateTrackingName(user.id, "chat-assistant") ``` ### Feature flag integration ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const getFeatureName = (featureFlags) => { const activeFeatures = Object.keys(featureFlags) .filter((key) => featureFlags[key]) .join("-"); return `app-${activeFeatures}`; }; ``` ### Version tracking ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Package.json version (replace dots with hyphens) const packageVersion = require("./package.json").version.replace(/\./g, "-"); name: `MyApp-v${packageVersion}` ``` ## Troubleshooting **Names not appearing in dashboard** * Check name follows alphanumeric + hyphens pattern. * Verify requests are being sent successfully. * Ensure name is under character limit (50 chars). **Fragmented tracking data** * Standardize naming conventions across team. * Use environment variables for consistency. * Implement a centralized naming function. **Too many unique names** * Avoid timestamps or random values. * Limit to \~50 unique names per account. * Use hierarchical naming instead of flat structure. ## Monitoring The following metrics are available for monitoring. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const appMetrics = { requestsByApp: {}, // Volume per application costsByApp: {}, // Spending per application latencyByApp: {}, // Performance per application errorsByApp: {}, // Error rates per application activeApps: new Set(), // Unique applications }; ``` ### Analytics queries The following queries can be answered using the above metrics. * Which applications use AI most? * What's the cost per application? * Which services have highest error rates? * How does performance vary by application? ## Best Practices ### Naming standards * Document naming conventions for your team. * Use consistent separators (hyphens recommended). * Include environment in name for clarity. * Avoid special characters or spaces. ### Example Implementation ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Centralized tracking configuration class OrqConfig { static getName(service, environment = process.env.NODE_ENV) { return `${service}-${environment}`; } static getConfig(service) { return { name: this.getName(service), }; } } // Usage const orqConfig = OrqConfig.getConfig("ChatBot"); ``` To make sure teams within an Engineering Organization align on App Tracking principles: * Maintain a list of approved application names. * Use code reviews to enforce naming standards. * Set up monitoring alerts for new/unexpected names. * Regular cleanup of unused tracking names. ## Limitations * **Name constraints**: Alphanumeric characters and hyphens only. * **Length limits**: Maximum 50 characters per name. * **Storage impact**: Many unique names increase metadata storage. * **Query performance**: Large numbers of unique names may slow filtering. * **No retroactive changes**: Historical traces keep original names. ## Integration Examples ### With external monitoring systems ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const prometheus = { histogram: (name: string, value: number, labels: Record) => {}, counter: (name: string, value: number, labels: Record) => {}, }; // replace with your Prometheus client, e.g. from prom-client // Export metrics by application const exportMetrics = (trackingName: string, responseTime: number, cost: number) => { prometheus.histogram("ai_request_duration", responseTime, { app: trackingName, }); prometheus.counter("ai_request_cost", cost, { app: trackingName }); }; ``` ### With logging ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} logger.info("AI request completed", { trackingName: "ChatBot-Prod", responseTime: 1250, model: "gpt-5.6-sol", success: true, }); ``` # Budgets Source: https://docs.orq.ai/docs/ai-gateway/budgets Set spending limits on any scope (workspace, project, identity, API key, provider, or model) to control AI costs across the organization. **Budgets** apply spending controls to a named target. Each budget defines a scope (what it applies to), period limits (how much can be spent or consumed per reset period), and an optional expiration date after which the budget stops enforcing. When a cost or token limit is reached, requests matching the budget's scope are blocked until the period resets. Requests per minute limits block for one minute only, independent of the reset period. Multiple budgets at different levels can apply to the same request at once, and each is enforced independently; see [Budget levels and interaction](#budget-levels-and-interaction). Common use cases: * Cap monthly spend per customer or tenant by scoping a budget to an **Identity**. * Prevent a staging or CI environment from consuming production credits by scoping a budget to its **API Key**. * Limit exposure to an expensive provider or model during evaluation by scoping a budget to a **Provider** or **Model**. * Enforce a hard workspace-wide cost ceiling as a safety net by scoping a budget to the **Workspace**. * Control spend for a time-boxed experiment using a **One-time** reset period with an expiration date. Navigate to **Settings > Organization > Budgets** to manage budgets. Only workspace admins can create, edit, or delete budgets. ## Overview The Budgets table lists all budgets in the organization. Budgets list showing budget rows with Target, Scope, Limits, Reset, Expires, and Updated columns. | Column | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Target** | The entity the budget applies to (e.g. the workspace, a project name, an API key name, an identity name, a provider, or a model name). | | **Scope** | The type of entity: Workspace, Project, Identity, API Key, Provider, or Model. | | **Limits** | The configured cost and token limits for the reset period. Click a budget row to [see current usage](#monitor-usage). | | **Reset** | The configured reset period: Daily, Weekly, Monthly, Yearly, or One-time (no automatic reset). | | **Expires** | Time remaining until the budget stops enforcing. Shown in red when expiration is approaching. After expiration the budget remains in the list but no longer enforces. Blank if the budget never expires. | | **Updated** | The date the budget was last modified. | Use Filter to narrow the list by **Period** (Daily, Monthly, One-time, Weekly, Yearly), **Scope**, or **Status** (Active, Expired). Use Sort by to reorder by **Expiry soon**, **Newest**, or **Recently updated**. ### Monitor usage Click a budget row to open its detail page: * A metric card for each configured limit (**Total spend**, **Tokens**, **Req per minute**) shows current usage, a progress bar, and percentage consumed. * **Scope**, **Resets** period, **Renews in** countdown, and **Expires** date appear at the top. Budget detail page for identity Jarmila showing 66% used of $0.20 total spend, 89% used of 25,000 tokens, 0% used of 5 requests per minute, and two alerts configured at 75% and 90% usage. * Click Adjust limits to edit the budget's period limits or expiration date. * Click Add threshold in the **Alerts** section to [set up an alert](#create-an-alert). * Click Delete Budget to permanently remove it. Enforcement stops immediately. ## Create a budget Click Create in the top-right corner of the Budgets page. Create budget dialog showing Scope, Period limits, and Expiration fields, with an empty Alerts section reading No alerts configured and a disabled Add threshold button. Under **Scope**, select what the budget **Applies to**: | Option | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------- | | **Workspace** | Applies to the entire workspace. | | **Project** | Applies to a specific project. | | **Identity** | Applies to a contact by external ID. | | **API Key** | Applies to a specific API key. | | **Provider** | Applies to all requests to a provider. Matches the provider segment of the model reference (`openai` in `openai/gpt-4o`). | | **Model** | Applies to a specific model, matched by the full model reference as sent in the request (`openai/gpt-4o`). | For all scope types except **Workspace**, a second field appears to select the specific target (for example, which API key or which identity). Only one budget is allowed per target. If a budget already exists for the selected target, an inline error is shown and the budget cannot be created until a different target is selected. See [Budget levels and interaction](#budget-levels-and-interaction) for how budgets at different levels apply to the same request. Under **Period limits**, set the **Reset period** (required) and one or more of the following limits: | Limit | Unit | Description | | ----------------------- | -------- | --------------------------------------------- | | **Cost** | USD | Maximum spend allowed per reset period. | | **Tokens limit** | K Tokens | Token cap per reset period, in thousands. | | **Requests per minute** | Req/min | Rate cap enforced every minute, at all times. | Leave a limit at `0` to leave it unconfigured. At least one limit must be set to a non-zero value to create the budget. Reset periods follow a fixed schedule: | Period | Resets at | | -------- | ------------------------------------------------------------- | | Daily | Midnight UTC | | Weekly | Monday midnight UTC | | Monthly | 1st of the month, midnight UTC | | Yearly | January 1st, midnight UTC | | One-time | Does not reset. Limit applies for the lifetime of the budget. | **Requests per minute** is a rolling rate cap enforced continuously, independent of the reset period. If a budget is exhausted before the period resets, an admin can increase its limit to unblock further usage immediately. Existing usage is not removed; the higher limit simply allows the entity to consume more within the current period. Under **Expiration**, set an **Expiration date** if the budget should stop enforcing after a specific date. Leave it as **Never expires** for an indefinite budget. Click Create budget. The budget appears in the list immediately and begins enforcing on the next request. ## Edit or delete a budget Select the menu on any budget row to: * **Edit**: update the period limits or expiration date. The scope and target cannot be changed after creation. * **Delete**: permanently removes the budget. Enforcement stops immediately. Budgets can also be created and updated programmatically through the API using a **Management Key**. Regular API keys cannot manage Budgets. ## Configure alerts **Alerts** notify configured notifiers when a budget's cost usage crosses a percentage threshold. The gateway performs two separate checks. Before a request starts, it verifies the budget hasn't been fully consumed. Requests are blocked once usage reaches 100%. Alert thresholds are evaluated after each request completes, which means a notification can lag slightly behind the moment the threshold was crossed. **Example:** An 80% cost threshold on a project's budget, notifying the project administrator by email, gives time to react before the budget blocks traffic at 100%. Alerts track cost usage only; token and requests-per-minute limits do not trigger them. A cost limit is required before adding an alert; Add threshold stays disabled until one is set. For alerts on latency, errors, or guardrail results, see the AI Studio [**Alerts**](/docs/ai-studio/observability/alerts) page. ### Create a notifier Notifiers must exist before they can be configured in an alert. Create one under **Settings > Organization > Notifiers**, or with the [**Notifiers API**](/reference/notifiers/create-a-notifier). Notifiers table showing a Webhook and an Email notifier, each with a Name, Destination, Type, and Updated date. Click Notifier. Enter a **Name** to identify it in the notifier list. Select **Send via** and configure the channel: | Channel | Fields | | ----------- | --------------------------------------------------- | | **Email** | One or more recipient email addresses. | | **Webhook** | A webhook URL, and optional custom request headers. | | **Slack** | A Slack incoming webhook URL. | Sending to a **Microsoft Teams** channel? See the [Microsoft Teams example](#send-notifications-to-a-microsoft-teams-channel). Click Create (or Save when editing an existing notifier). ### Create an alert The **Alerts** section is on the budget's create/edit panel, and on its detail page (open a budget by clicking its row in the Budgets list). In the **Alerts** section, click Add threshold. Enter a whole number between 1 and 100. Each budget allows only one alert per threshold percentage; duplicate thresholds are rejected. Click Add notifiers and search for one or more already-created notifiers to receive the alert. At least one notifier is required. Each alert allows a maximum of 10 notifiers. Click Add alert (or Save changes when editing an existing alert). Create alert dialog showing a 90% threshold, a Webhook notifier chip, and Add notifiers, Cancel, and Add alert buttons. Existing alerts appear as rows in the **Alerts** section. Select to edit a threshold or its notifiers, or to delete the alert. ## Examples ### Send notifications to a Microsoft Teams channel Route alert notifications to a **Microsoft Teams** channel with a generic webhook notifier. Teams renders messages in its own card format, so the webhook points at a small relay that converts **Orq.ai**'s notification payload before forwarding it to the channel. In the target **Microsoft Teams** channel, open **Workflows** (or **Power Automate**), search for the **Post to a channel when a webhook request is received** template, complete the setup, and copy the webhook URL it generates. **Orq.ai** sends the raw notification envelope as JSON, and Teams only accepts its own message card format, so the webhook cannot point at the Teams URL directly. Deploy a small relay that converts the payload: ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} export default { async fetch(request, env) { if (request.method !== "POST") { return new Response("Method not allowed", { status: 405 }); } let envelope; try { envelope = await request.json(); } catch { return new Response("Invalid JSON body", { status: 400 }); } const links = envelope.links ?? []; const card = { "@type": "MessageCard", "@context": "http://schema.org/extensions", themeColor: severityColor(envelope.severity), title: envelope.title, text: envelope.body, sections: [ { facts: [ { name: "Severity", value: envelope.severity }, ...links.map((link) => ({ name: link.text, value: link.url })), ], }, ], }; const teamsResponse = await fetch(env.TEAMS_WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(card), }); if (!teamsResponse.ok) { // Relay the status so Orq.ai retries the delivery. console.log(`Teams rejected the message: ${teamsResponse.status}`); return new Response(`Teams rejected the message: ${teamsResponse.status}`, { status: teamsResponse.status, }); } console.log(`Forwarded notification ${envelope.id} to Teams (${teamsResponse.status})`); return new Response("OK", { status: 200 }); }, }; function severityColor(severity) { switch (severity) { case "critical": return "B71C1C"; case "warning": return "F57C00"; case "ok": return "2E7D32"; default: return "1565C0"; } } ``` Set `TEAMS_WEBHOOK_URL` to the webhook URL from the previous step. The example deploys as a Cloudflare Worker; any HTTP endpoint that accepts JSON works. Under **Settings > Organization > Notifiers**, click Notifier, enter a **Name**, select **Webhook** as the **Send via** channel, and paste the relay URL as the **Webhook URL**. Custom request headers are optional. To create the notifier through the API: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --location 'https://my.orq.ai/v2/notifiers' \ --header "Authorization: Bearer $ORQ_API_KEY" \ --header 'Content-Type: application/json' \ --data-raw '{ "display_name": "Teams alerts", "type": "NOTIFIER_TYPE_WEBHOOK", "webhook_url": "https://teams-relay.example.workers.dev" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_API_KEY ?? "" }); const notifier = await orq.notifiers.create({ displayName: "Teams alerts", type: "NOTIFIER_TYPE_WEBHOOK", webhookUrl: "https://teams-relay.example.workers.dev", }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.getenv("ORQ_API_KEY")) notifier = orq.notifiers.create( request={ "display_name": "Teams alerts", "type": "NOTIFIER_TYPE_WEBHOOK", "webhook_url": "https://teams-relay.example.workers.dev", } ) ``` See the [API reference](/reference/notifiers/create-a-notifier) for the full parameter specification. Add the notifier to a [budget alert](#create-an-alert) under **Add notifiers**, or to an **Observability** [Alert](/docs/ai-studio/observability/alerts) under **Notify via**. Each alert accepts up to 10 notifiers. Create a budget with a small cost limit and an alert at a low threshold, then make a request that crosses it. The alert fires on the next check and the message lands in the channel. If nothing arrives, check the relay logs first: they record every converted payload, including any rejection from Teams. **Payload format**: **Orq.ai** POSTs the notification envelope as `application/json` with `X-Orq-Hook-ID`, `X-Orq-Event`, and `Idempotency-Key` headers, plus any headers configured on the notifier: ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "id": "ntf_01JQ8Z2K4M6P8R0T2V4X6Z8B0D", "version": 1, "workspace_id": "ws_01JQ8Z0MB5W1YTVN3GCK7B0EXA", "timestamp": "2026-08-08T09:30:00Z", "title": "Budget usage has reached your notification threshold", "body": "Your usage today for workspace acme has reached $92.00 (92%) of your $100.00 budget. This alert is configured at 90% ($90.00).", "severity": "warning", "links": [ { "text": "Manage budgets", "url": "https://my.orq.ai/budgets" } ], "tags": { "budget_id": "bgt_01JQ8Z0MB5W1YTVN3GCK7B0EXA", "scope_kind": "WORKSPACE", "period": "DAILY", "threshold_percent": "90" } } ``` Teams rejects this envelope with HTTP `400` because it expects a message card with `@type` and `@context`. The relay converts the envelope into that format before forwarding. **Troubleshooting**: | Symptom | Cause | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | Teams responds with `400` | The relay is missing or misconfigured. Confirm the relay actually maps the envelope to a MessageCard before checking anything else. | | Nothing arrives in the channel | The webhook URL is stale or revoked, or the relay crashed. Check the relay logs for the delivered and rejected requests. | | Notifications arrive late | Non-`2xx` responses are retried, so a failing relay delays delivery until it recovers. | ## Budget levels and interaction Budgets can be set at six levels: **Workspace**, **Project**, **Identity**, **API Key**, **Provider**, and **Model**. When several budgets apply to the same request, every one of them is enforced independently and cumulatively. There is no precedence: a budget at one level never overrides a budget at another level, and a request must satisfy every budget that matches it. Because only one budget is allowed per target (see [Create a budget](#create-a-budget)), a request can match at most one scoped budget per level, up to six in total. When a budget is exhausted, only requests that match it are rejected. ### How multiple budgets interact * **Every applicable budget is enforced.** A request must pass every budget that matches it. No budget overrides another. * **Each budget tracks its own usage.** A request counts toward every budget that applies to it, at the same time. * **The most restrictive budget binds.** The effective cap for a dimension is the most restrictive applicable budget. Limits on different dimensions (cost, tokens, requests per minute) all apply simultaneously. * **Requests per minute are enforced per budget**, each with its own rolling 60-second window. * **Expired budgets stop enforcing.** After the expiration date a budget no longer blocks requests, even though it stays in the list. ### Worked examples The workspace has a \$1,000/month budget. The **Production** project has a \$300/month budget. * Requests to Production count toward both budgets; requests to other projects count only toward the workspace budget. * When Production reaches \$300, requests to Production are rejected until the monthly reset. Other projects keep working until the workspace budget is exhausted at \$1,000. The **Support** project has a \$500/month budget. The identity `customer-acme` has a \$50/month budget. * The identity budget applies across all projects: the \$50/month cap is enforced whenever `customer-acme` makes a request, regardless of project. * Requests from `customer-acme` count toward both budgets. When the identity budget is exhausted, those requests are rejected even though the project budget has room; other identities keep working. The workspace has a \$2,000/month budget. The model `anthropic/claude-sonnet-5` has an \$800/month budget. * When the model budget is exhausted, requests to that model are rejected; other models keep working. * When total workspace spend reaches \$2,000, all requests are rejected until the monthly reset, regardless of model. ### When a limit is exceeded A request that matches an exhausted budget is rejected with `429 Too Many Requests`. The response identifies the budget and the exceeded dimension (`cost`, `tokens`, or `requests`); the response headers report the limit, remaining usage, and seconds until reset. Only requests that match the exhausted budget are blocked. | Dimension | Behavior when exceeded | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Cost** and **Tokens** | Counters update as requests complete. When a budget reaches 100%, the next matching request is rejected. Requests stay blocked until the budget's period resets, or an admin raises the limit. | | **Requests per minute** | Requests are rejected for the rolling 60-second window only, independent of the reset period. | For budgets with a **One-time** reset period, there is no automatic reset: matching requests stay blocked until an admin raises the limit, deletes the budget, or its expiration date passes. ### Reset periods Each budget has its own reset period and its own counters. Budgets at different levels can use different periods: **Daily**, **Weekly**, **Monthly**, **Yearly**, or **One-time** (no reset). See [Create a budget](#create-a-budget) for the reset schedule. ## See also What the **AI Gateway** enforces itself versus upstream providers, and the 429 response contract. Create and manage AI Gateway API keys with optional spending limits. Manage the workspace credit balance, payment methods, and auto top-up. # API Keys and Management Keys Source: https://docs.orq.ai/docs/ai-gateway/configuration/api-keys Create and manage project-scoped API keys and workspace-scoped management keys with granular permissions. ## API Keys ### What are API Keys API Keys are secure tokens used to authenticate requests to **Orq.ai**. Each key is scoped to a single project and carries a set of permissions that control what it can do. Two key types are available, chosen by the key's owner. #### User keys Tied to a specific user account and project. Automatically revoked if the user is removed from the organization or project. Use user keys for personal use and local development. #### Service account keys Not tied to any individual user, with a lifecycle independent of user membership. Only workspace admins can create service account keys. Use service account keys for production systems, so access does not break when a team member leaves. ### Viewing API Keys Navigate to **Settings > API Keys** to see all keys across projects. The table shows the following columns: | Column | Description | | --------------- | ----------------------------------- | | **Created** | Date the key was created | | **Name** | Key label | | **Type** | `User` or `Service` | | **Status** | `Active`, `Disabled`, or `Revoked` | | **Permissions** | `All`, `Read only`, or `Restricted` | | **Created by** | User who created the key | Use the menu to filter by Type or Permissions. ### Creating an API Key Navigate to **Settings > API Keys** and select Create API key. Create API key panel showing fields for owner, name, project, permissions, and expiration. Select You to create a User key, or Service account to create a key not tied to any individual user. Enter a **Name** for the key and select the [**Project**](/docs/ai-studio/get-started/projects) it will have access to. Choose a permission preset. See [Permissions](#permissions) below. Set an **Expiration** date if the key should automatically become inactive after a certain date. Select Create API key. A **Save your key** dialog appears showing the token and its permissions summary. Save your key dialog showing the API key token with a Copy button and a Permissions summary reading Read and write API resources. Click Copy to copy the token. The token is only shown once. Store it securely before closing this dialog. It cannot be retrieved afterwards. To cap a key's **cost**, **token**, or **requests-per-minute** usage, attach a [Budget](/docs/ai-gateway/budgets) to it. Budget limits reset on the configured period. See [Rate limits & quotas](/docs/ai-gateway/features/rate-limits) for what the gateway enforces at each layer. ### Permissions | Preset | Description | | -------------- | ------------------------------------------ | | **All** | Read and write access to all API resources | | **Read only** | Read access to all API resources | | **Restricted** | Custom per-resource access | With **Restricted**, set the permission for each resource individually: **None**, **Read**, or **Write**. **Write** permission automatically includes **Read** where Read is available for the endpoint. | Resource | None | Read | Write | | ----------------- | :-------------------------------: | :----------------------------: | :-----------------------: | | Agent schedules | ✓ | ✓ | ✓ | | Agents | ✓ | ✓ | ✓ | | Annotations | ✓ | ✓ | ✓ | | Chunking | ✓ | ✓ | ✓ | | Datasets | ✓ | ✓ | ✓ | | Deployments | ✓ | ✓ | ✓ | | Evaluators | ✓ | ✓ | ✓ | | Feedback | ✓ | ✓ | ✓ | | Files | ✓ | ✓ | ✓ | | Guardrail rules | ✓ | ✓ | ✓ | | Human evaluations | ✓ | ✓ | ✓ | | Identities | ✓ | ✓ | ✓ | | Knowledge bases | ✓ | ✓ | ✓ | | Memory stores | ✓ | ✓ | ✓ | | Policies | ✓ | ✓ | ✓ | | Projects | ✓ | ✓ | ✓ | | Prompts | ✓ | ✓ | ✓ | | Reporting | ✓ | ✓ | | | Routing rules | ✓ | ✓ | ✓ | | Skills | ✓ | ✓ | ✓ | | Tools | ✓ | ✓ | ✓ | | **Gateway** | | | | | Chat completions | ✓ | | ✓ | | Embeddings | ✓ | | ✓ | | Images | ✓ | | ✓ | | Models | ✓ | ✓ | | | Moderations | ✓ | | ✓ | | OCR | ✓ | | ✓ | | Rerank | ✓ | | ✓ | | Responses | ✓ | ✓ | ✓ | | Speech | ✓ | | ✓ | | Transcriptions | ✓ | | ✓ | ### Managing Keys API Keys management table listing keys with columns for name, type, status, permissions, and created by. Select the menu on any key to access the following actions: * Edit: update the name, permissions, or expiration date. The owner type cannot be changed after creation. * Duplicate: opens the creation panel pre-filled with the key's current settings. * Delete: permanently removes the key. This cannot be undone. A **Revoked** status means the key belonged to a user who was removed from the organization or project, or it has been revoked manually. Revocation is permanent. ## Management Keys ### Overview Management Keys are only available to workspace admins. The **Management Keys** page is not visible to non-admin members. **Management Keys** are workspace-scoped tokens for authenticating workspace administration operations. They can manage **API keys**, **Budgets**, **Projects**, **Smart Routers**, **Evaluators**, **Workspace models**, and **Workspace Settings**. Management Keys cannot be used to query models or agents. Use a standard [API Key](/docs/ai-studio/organization/api-keys) for inference and product endpoints. #### Use cases * Automating workspace provisioning via the API. * Delegating API key rotation to a deployment pipeline without granting full admin access. * Letting a billing automation script read and update Budgets without exposing API key management. #### Workspace administration endpoints Standard API keys are project-scoped. Use a Management Key for these workspace administration endpoints: | Endpoint family | Management Key access | | -------------------- | -------------------------------------------------------------------------------------------------------------- | | `/v2/api-keys*` | List, retrieve, create, update, and delete API keys. `/v2/api-keys/capabilities` is public. | | `/v2/budgets*` | List, retrieve, create, update, reset, and delete Budgets. | | `/v2/projects*` | Manage Projects across the workspace. A project-scoped API key can only list or retrieve its selected Project. | | `/v2/smart-routers*` | List, retrieve, create, update, enable, disable, and delete Smart Routers. | Notifiers are intentionally not listed here: their routes are project-scoped and do not require a Management Key. Project-scoped API keys can access the Notifiers API for their selected project. ### View Management Keys Navigate to **Settings** → **Organization** → **Management Keys** to view all keys in the workspace. Management Keys list with columns for Created date, Name, Status, Permissions, and Created by. Click any row to open the edit panel and update the key's name, permissions, or expiration date. Hover a row and click for additional options: * Edit: open the edit panel to update the key's name, permissions, or expiration date. * Duplicate: create a new key with the same permissions. * Delete: permanently remove the key from the workspace. ### Create a Management Key Navigate to **Settings** → **Organization** → **Management Keys** and click New key. Create new key dialog showing Name field, Permissions toggle with All, Restricted, and Read only options, API keys and Budgets capability rows, and an Expiration date field. Enter a **Name** for the key (required, max 128 characters). Select a **Permissions** mode, default is All. See [Permission modes](#permission-modes) below. Set an **Expiration** date if the key should stop authenticating after a certain date. Click Create key. A **Save your key** panel appears showing the token. The token is only shown once. Store it securely before closing this panel. It cannot be retrieved afterwards. #### Permission modes | Mode | Description | | -------------- | -------------------------------------------------------------------------------------------------- | | **All** | Full read and write access to all capabilities. | | **Restricted** | Configure access per capability. Each capability can be set to None, Read, or Write independently. | | **Read only** | Read access to all capabilities. | #### Capabilities | Capability | None | Read | Write | | ---------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | **API keys** | No access. | List and view workspace **API keys**. | List, view, create, update, revoke, and delete workspace **API keys**. | | **Budgets** | No access. | List and view workspace **Budgets**. | List, view, create, update, reset, and delete workspace **Budgets**. | | **Projects** | No access. | List and view workspace **Projects**. | List, view, create, update, and delete workspace **Projects**. | | **Smart Routers** | No access. | List and view workspace **Smart Routers**. | List, view, create, update, enable, disable, and delete workspace **Smart Routers**. | | **Evaluators** | No access. | List and view workspace **Evaluators**. | List, view, create, update, and delete workspace **Evaluators**. | | **Workspace models** | No access. | No list or view endpoints exist for workspace **models**; read access alone performs no API operations. | Enable and disable workspace **models** and manage their sharing. | | **Workspace Settings** | No access. | View workspace settings. | View and update workspace settings. | Management Keys are not available through MCP. Use the REST API to manage keys programmatically. ## Manage keys programmatically Create, update, revoke, and delete API Keys and [Management Keys](/docs/ai-studio/organization/management-keys) with the API instead of the console. The full endpoint reference is on the [API Keys reference](/reference/api-keys/create-a-new-api-key) and [Management Keys reference](/reference/management-keys/create-a-new-management-key) pages. ### Authenticating with a Management Key Key management requests authenticate with a [**Management Key**](/docs/ai-studio/organization/management-keys), never an API key. **API Keys** are project-scoped inference credentials and cannot administer keys. * Pass a **Management Key** as the bearer token, or as the SDK API key. * A **Management Key** with the `api-key` write grant can list, create, update, revoke, and delete **API Keys**. * Managing **Management Keys** requires the `management-key` grant, which is not part of the **All** or **Read only** presets. Grant it explicitly to a **Management Key** that must administer other Management Keys. The SDK exposes these operations as `api_keys` / `apiKeys` and `management_keys` / `managementKeys`: [API Keys SDK](/reference/sdk/apikeys), [Management Keys SDK](/reference/sdk/managementkeys). ### Key lifecycle in code These examples use a **Management Key** as `ORQ_API_KEY`. | Operation | Endpoint | Notes | | --------- | ---------------------------------- | ------------------------------------------------------------------- | | Create | `POST /v2/api-keys` | `name` is required; scope, permissions, and expiration are optional | | Disable | `PATCH /v2/api-keys/{api_key_id}` | Set `status` to `API_KEY_STATUS_DISABLED`; reversible | | Revoke | `PATCH /v2/api-keys/{api_key_id}` | Set `status` to `API_KEY_STATUS_REVOKED`; permanent | | Delete | `DELETE /v2/api-keys/{api_key_id}` | Permanently removes the key | | List | `GET /v2/api-keys` | Paginated; never returns the secret | | Retrieve | `GET /v2/api-keys/{api_key_id}` | Never returns the secret | Create a project-scoped key with restricted permissions: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/api-keys \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Support automation key", "project_scope": { "single": { "project_id": "proj_01HZXW2K7Y8Q9M0N1P2R3S4T5V" } }, "permission_mode": "PERMISSION_MODE_RESTRICTED", "access": { "agent": "ACCESS_LEVEL_WRITE", "deployment": "ACCESS_LEVEL_READ" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const client = new Orq({ apiKey: process.env.ORQ_API_KEY, }); const result = await client.apiKeys.create({ name: "Support automation key", projectScope: { single: { projectId: "proj_01HZXW2K7Y8Q9M0N1P2R3S4T5V" }, }, permissionMode: "PERMISSION_MODE_RESTRICTED", access: { agent: "ACCESS_LEVEL_WRITE", deployment: "ACCESS_LEVEL_READ", }, }); console.log(result.id); console.log(result.token); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from orq_ai_sdk import Orq client = Orq(api_key=os.environ["ORQ_API_KEY"]) result = client.api_keys.create( name="Support automation key", project_scope={ "single": {"project_id": "proj_01HZXW2K7Y8Q9M0N1P2R3S4T5V"}, }, permission_mode="PERMISSION_MODE_RESTRICTED", access={ "agent": "ACCESS_LEVEL_WRITE", "deployment": "ACCESS_LEVEL_READ", }, ) print(result.id) print(result.token) ``` Disable, revoke, or delete an existing key: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} # Disable (reversible) curl -X PATCH https://my.orq.ai/v2/api-keys/01HZXW2K7Y8Q9M0N1P2R3S4T5V \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "API_KEY_STATUS_DISABLED"}' # Revoke (permanent) curl -X PATCH https://my.orq.ai/v2/api-keys/01HZXW2K7Y8Q9M0N1P2R3S4T5V \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "API_KEY_STATUS_REVOKED"}' # Delete curl -X DELETE https://my.orq.ai/v2/api-keys/01HZXW2K7Y8Q9M0N1P2R3S4T5V \ -H "Authorization: Bearer $ORQ_API_KEY" ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Disable (reversible) await client.apiKeys.update({ apiKeyId: "01HZXW2K7Y8Q9M0N1P2R3S4T5V", updateApiKeyRequest: { status: "API_KEY_STATUS_DISABLED" }, }); // Revoke (permanent) await client.apiKeys.update({ apiKeyId: "01HZXW2K7Y8Q9M0N1P2R3S4T5V", updateApiKeyRequest: { status: "API_KEY_STATUS_REVOKED" }, }); // Delete await client.apiKeys.delete({ apiKeyId: "01HZXW2K7Y8Q9M0N1P2R3S4T5V" }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} # Disable (reversible) client.api_keys.update( api_key_id="01HZXW2K7Y8Q9M0N1P2R3S4T5V", status="API_KEY_STATUS_DISABLED", ) # Revoke (permanent) client.api_keys.update( api_key_id="01HZXW2K7Y8Q9M0N1P2R3S4T5V", status="API_KEY_STATUS_REVOKED", ) # Delete client.api_keys.delete(api_key_id="01HZXW2K7Y8Q9M0N1P2R3S4T5V") ``` To rotate a key, create a replacement key with the same configuration, then revoke the old one. Rotation is create followed by revoke; there is no dedicated rotate endpoint. ### Permission model `permission_mode` selects the preset: | Mode | Description | | ---------------------------- | ---------------------------------------------------------------------- | | `PERMISSION_MODE_ALL` | Read and write access to all domains in the API key capability catalog | | `PERMISSION_MODE_READ_ONLY` | Read access to all domains in the API key capability catalog | | `PERMISSION_MODE_RESTRICTED` | Per-domain access via the `access` map | With `PERMISSION_MODE_RESTRICTED`, the `access` map assigns a level to each domain: `ACCESS_LEVEL_NONE`, `ACCESS_LEVEL_READ`, or `ACCESS_LEVEL_WRITE`. Write includes read. REST JSON and the SDKs accept the enum names: | Level | Access | | -------------------- | --------------------- | | `ACCESS_LEVEL_NONE` | No access | | `ACCESS_LEVEL_READ` | Read access | | `ACCESS_LEVEL_WRITE` | Read and write access | The capability catalog lists the domains a key can be granted: `GET /v2/api-keys/capabilities` returns each domain with its id, display name, group, and read / write availability. Use the catalog to discover grantable domains at runtime instead of hard-coding the list. ### The secret is returned once The create response returns the raw token exactly once, in the `sk-orq--` format. Store it immediately: the API stores only a displayable `token_prefix` and a hash of the secret, so list and retrieve responses never include the token. The token is only returned in the create response. Store it securely and treat it like a password; it cannot be retrieved afterwards. ### Cost, token, and rate limits Keys do not carry limit fields. Attach cost, token, and requests-per-minute limits with a [Budget](/docs/ai-gateway/budgets) scoped to the key. **Budgets** are workspace resources and require a [**Management Key**](/docs/ai-studio/organization/management-keys); regular **API Keys** cannot manage them. ### Example: provision a per-customer key with a spend cap Create a key scoped to the customer's project, then attach a monthly spend cap as a Budget scoped to that key: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} # Create the customer key; capture the key id and token from the response curl -X POST https://my.orq.ai/v2/api-keys \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Acme key", "project_scope": { "single": { "project_id": "proj_01HZXW2K7Y8Q9M0N1P2R3S4T5V" } }, "permission_mode": "PERMISSION_MODE_RESTRICTED", "access": { "agent": "ACCESS_LEVEL_WRITE" } }' # Cap cost, tokens, and rate with a Budget scoped to the key curl -X POST https://my.orq.ai/v2/budgets \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "scope": { "api_key": { "api_key_id": "" } }, "limits": { "period": "BUDGET_PERIOD_MONTHLY", "amount": 100, "token_limit": 1000000 }, "rate_limit": { "requests_per_minute": 60 } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const key = await client.apiKeys.create({ name: "Customer Acme key", projectScope: { single: { projectId: "proj_01HZXW2K7Y8Q9M0N1P2R3S4T5V" }, }, permissionMode: "PERMISSION_MODE_RESTRICTED", access: { agent: "ACCESS_LEVEL_WRITE", }, }); console.log(key.token); await client.budgets.create({ scope: { apiKey: { apiKeyId: key.id } }, limits: { period: "BUDGET_PERIOD_MONTHLY", amount: 100, tokenLimit: 1000000, }, rateLimit: { requestsPerMinute: 60 }, }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} key = client.api_keys.create( name="Customer Acme key", project_scope={ "single": {"project_id": "proj_01HZXW2K7Y8Q9M0N1P2R3S4T5V"}, }, permission_mode="PERMISSION_MODE_RESTRICTED", access={ "agent": "ACCESS_LEVEL_WRITE", }, ) print(key.token) client.budgets.create( scope={"api_key": {"api_key_id": key.id}}, limits={ "period": "BUDGET_PERIOD_MONTHLY", "amount": 100, "token_limit": 1000000, }, rate_limit={"requests_per_minute": 60}, ) ``` # Guardrail Rules Source: https://docs.orq.ai/docs/ai-gateway/configuration/guardrail-rules Configure guardrail rules in the AI Gateway to validate and control LLM requests and responses with evaluators triggered by CEL conditions. Guardrail Rules define conditions under which [**Evaluators**](/docs/ai-studio/optimize/evaluators) (automated checks that inspect a request or response) run against requests passing through the [**AI Gateway**](/docs/ai-gateway/get-started/introduction). Conditions are written as CEL expressions, built visually with the Rule Builder covered below. A guardrail is only triggered when its rule conditions are matched, not on every request. ## Use cases Guardrail rules are most useful when the same safety or compliance check needs to apply consistently across many requests. Runs a jailbreak detection [**Evaluator**](/docs/ai-studio/optimize/evaluators) on all customer-facing requests at the gateway level, adding an extra security layer across **AI Gateway** traffic. Enforces GDPR compliance by running PII detection on all matching requests workspace-wide from a single rule. Validates customer detail access for the sales team by calling an external [**Evaluator**](/docs/ai-studio/optimize/evaluators) on every matching request before it reaches the model. Applies a tone of voice [**Evaluator**](/docs/ai-studio/optimize/evaluators) at the gateway level so every response is checked against the company's tone guidelines. Runs a compliance [**Evaluator**](/docs/ai-studio/optimize/evaluators) on EU-routed requests only, scoped using the Rule Builder so the guardrail applies exactly where it is needed without affecting other traffic. Runs jailbreak detection and response relevance [**Evaluators**](/docs/ai-studio/optimize/evaluators) at 50% sample rate each, scoped to specific traffic using a metadata condition in the Rule Builder. ## Visibility * Visible to workspace administrators only. ## Creating a guardrail rule From the **Guardrail Rules** list, click Add New Rule. A panel opens on the right with the following fields. Create Guardrail Rule ### General | Field | Description | | --------------- | ------------------------------------------------------------------------------------------------------------- | | **Rule Name** | A display name for the guardrail rule. | | **Description** | Optional context for administrators. | | **Enable Rule** | Toggle to activate or deactivate the rule. When enabled, the rule is active and applied to matching requests. | ### Evaluators Select the [**Evaluators**](/docs/ai-studio/optimize/evaluators) to run when this rule is triggered. Click Add to attach one or more evaluators from the scoped project. The Add menu groups options into **System** and **Workspace**: Workspace evaluators are the project's custom evaluators; System guardrails are covered below. #### System Guardrails System Guardrails are the pre-built checks **Orq.ai** maintains in the Evaluators list's System group. Attach one and it runs immediately as a pass/fail check that can block a request; it never rewrites content. | System Guardrail | What it checks | Configurable | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | **PII Detection** | Detects personally identifiable information in requests or responses and blocks the request by default, instead of redacting it. | Yes. Once added, a icon appears next to it in the Evaluators list. Click it to set `language`, `threshold`, and `entities`. | | **Secret Detection** | Detects API keys, tokens, credentials, and other secrets in requests or responses and blocks the request by default. | No configurable options. | Toggle the icon next to a System Guardrail in the Evaluators list to switch it to monitoring-only mode. The same check still runs and its result is still recorded, but a match no longer blocks the request. The detectable entity catalog for **PII Detection** is shared with the [**PII Redaction**](/docs/ai-gateway/features/plugins/pii-redaction#supported-entity-types) plugin, and is region-scoped rather than language-scoped. `GET /v2/pii/capabilities` is the live source of truth for the supported regions, base and regional entity types, and the `region_entities` mapping. A few [entity type names changed](/docs/ai-gateway/features/plugins/pii-redaction#entity-type-names-that-changed) when the catalog moved to regions; the old keys are rejected at write time. Each guardrail runs on the request **Input**, the model **Output**, or both. Click the icon next to a guardrail in the Evaluators list to cycle through **Input**, **Output**, and **Both**, set independently per guardrail. System Guardrails always fail closed: if the underlying check errors (for example the detection service is unavailable or the call itself fails), the guardrail blocks the request instead of letting it through unchecked. ### Rule Builder The Rule Builder constructs the match conditions that determine when the guardrail is triggered. Clicking Add opens a dropdown with the following condition types: | Condition | Description | | ------------ | ------------------------------------------ | | **Header** | Match on a request header name and value. | | **Model** | Match on the model being called. | | **Identity** | Match on the identity making the request. | | **Metadata** | Match on metadata attached to the request. | | **Project** | Match on the project scope of the request. | You can also click **Add group** to nest conditions into a logical group. Multiple conditions within a group are joined with `and`. Groups themselves can be joined with either `and` or `or`. Select the operator between groups to control how they combine. Each condition can be removed with . The builder generates a CEL (Common Expression Language) expression shown read-only in the **CEL Expression Preview** below. The guardrail is only triggered when the expression evaluates to true. # Guardrails Source: https://docs.orq.ai/docs/ai-gateway/configuration/guardrails Create LLM and Python guardrails in the AI Gateway to evaluate and block non-compliant requests and responses. **Guardrails** in the **AI Gateway** are LLM-as-a-Judge and Python evaluators that validate requests and responses passing through the gateway. Once created, they can be attached to [Guardrail Rules](/docs/ai-gateway/configuration/guardrail-rules) to block non-compliant generations before they reach the caller. Use a model to judge outputs against any criteria defined in a prompt. Write custom Python code for full flexibility: regex checks, length validation, HTTP calls, or JSON schema validation. ## Execution behavior Set each Guardrail to **Input**, **Output**, or **Both** when adding it to a [Guardrail Rule](/docs/ai-gateway/configuration/guardrail-rules). Input Guardrails, including Secret Detection, run synchronously on the complete input before the **AI Gateway** sends the request to the model. Streaming does not change this behavior. Output Guardrails run after generation and before the response for non-streaming requests. Output Guardrails do not run on streaming responses. A secret can span multiple chunks, so a chunk-by-chunk check cannot inspect the complete output. Use a non-streaming request when output enforcement is required. ## LLM Guardrail LLM Guardrails use a model to judge requests or responses against criteria defined in a prompt. Navigate to **Guardrails** in the **AI Gateway** sidebar, click + Guardrail, and select **LLM**. Fill in the following fields: | Field | Description | | --------------- | -------------------------------------------------------------------------------------------------------------- | | **Key** | Unique identifier for the guardrail | | **Description** | Optional context | | **Model** | The model used as judge. Any model enabled in the [AI Gateway](/docs/ai-gateway/using-the-router) is available | ### Configure Prompt Reference the evaluated run with the following **string** variables. Type `{{` in the prompt editor to pick one from the full list. | Variable | Description | | ------------------------------- | --------------------------------------------------------------------------- | | `{{input.user_query}}` | The last message sent to the model | | `{{input.all_messages}}` | The full conversation, including the graded turn | | `{{input.system_instructions}}` | The system prompt used for the run | | `{{input.retrievals}}` | [Knowledge Base](/docs/ai-studio/ai-engineering/knowledge-bases) retrievals | | `{{input.expected_output}}` | The reference used to compare output | | `{{output.response}}` | The output response generated by the evaluated model | | `{{output.tools_called}}` | The tool calls made during the run, with their results | See [Structured variable shapes](/docs/ai-studio/optimize/evaluators#structured-variable-shapes) for the fields available when indexing messages and tool calls. The `log.*` variables remain supported, so existing Guardrails keep working. Prefer the variables above for new ones. | Variable | Description | | -------------------- | -------------------------------------------------------------------------------- | | `{{log.input}}` | Same as `{{input.user_query}}` | | `{{log.output}}` | Same as `{{output.response}}` | | `{{log.retrievals}}` | Same as `{{input.retrievals}}` | | `{{log.reference}}` | Same as `{{input.expected_output}}` | | `{{log.tool_calls}}` | Same as `{{output.tools_called}}` | | `{{log.messages}}` | The conversation **without** the graded turn. `{{input.all_messages}}` keeps it. | ### Output and Guardrail Configuration Select the output type and set the pass condition. The **Guardrail configuration** panel is visible directly in the settings. The model returns a **True** or **False** response. Use for binary pass/fail checks. **Pass condition**: Select **True** or **False**. The guardrail passes when the model returns the selected value. The model returns a numeric score. Use any scale that fits the use case (e.g. 1-5, 0-100). **Pass condition**: Enter a threshold in **Pass if greater or equal than**. The guardrail passes when the score meets or exceeds the threshold. The model classifies the output into one of the predefined labels. When **Categorical** is selected, a label editor appears. Add one label per row: enter a **Value** (the exact string the model must return) and an optional **Description** to guide the model. **Pass condition**: Select one or more values in **Pass if output is one of**. The guardrail passes when the model's output matches any selected label. The model returns a free-form string response. Not available as a guardrail pass condition. ### Testing The **Playground** panel provides an **Editor** for testing. Fill the payload manually: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "messages": [], "input": "", "retrievals": [], "output": "", "reference": "" } ``` Click **Run test** to execute the guardrail. The result appears in the **Response** field. The Dataset tab is not available for Guardrails. ## Python Guardrail Python Guardrails accept custom **Python code** for full evaluation flexibility. The UI has three panels: **Settings**, **Code**, and **Playground**. Python code is limited to 1 MB (1,048,576 bytes) per guardrail: roughly 1 million characters, or about 20,000 lines of typical Python. Larger code returns a `Code exceeds maximum size` error and does not run. Navigate to **Guardrails** in the **AI Gateway** sidebar, click + Guardrail, and select **Python**. The evaluation function receives a `log` object with the following fields: * `log["input"]` ``: the last message sent to generate the output * `log["output"]` ``: the generated response from the model * `log["reference"]` ``: the reference used to compare the output * `log["messages"]` `list`: all previous messages sent to the model * `log["retrievals"]` `list`: all [Knowledge Base](/docs/ai-studio/ai-engineering/knowledge-bases) retrievals The function must return a **Boolean** or **Number**: ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} def evaluate(log): return True ``` Define multiple helper functions if needed. The last defined function is the entry point when the guardrail runs. ### Environment and Libraries The Python Guardrail runs in **Python 3.12** with the following preloaded libraries: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} numpy==1.26.4 nltk==3.9.1 requests pydantic json re ``` ### Guardrail Configuration Set the pass condition based on the return type: * **Boolean**: select **True** or **False**. The guardrail passes when the function returns the selected value. * **Number**: enter a score threshold. The guardrail passes when the return value is greater than or equal to the threshold. ### Testing The **Playground** panel provides an **Editor** for testing. Fill the payload manually and click **Run test** to execute the guardrail. The Dataset tab is not available for Guardrails. ## Versions Click Publish to save changes. Choose a version bump: * **Patch** (e.g. `v1.0.0` → `v1.0.1`): small fixes, no behavior change * **Minor** (e.g. `v1.0.0` → `v1.1.0`): new functionality, backwards compatible * **Major** (e.g. `v1.0.0` → `v2.0.0`): breaking change or significant rework The **Versions** tab shows the full history with author and publish timestamp for each version. ### Restoring a Version Click the compare icon on any version in the **Versions** tab to open **Compare**, then click Restore next to an older version to load it into the current working draft. Restore does not publish automatically: the guardrail is loaded into the draft as unpublished changes on the **Settings** tab, and Publish still needs to be clicked for it to become a real version. Earlier versions are never deleted, so restoring is always reversible. If there are unpublished changes already, a confirmation dialog asks for confirmation before overwriting them. ## Using Guardrails Guardrails created here are available for selection when configuring [Guardrail Rules](/docs/ai-gateway/configuration/guardrail-rules). # Routing Rules Source: https://docs.orq.ai/docs/ai-gateway/configuration/routing-rules Use CEL-based routing rules to redirect AI Gateway requests to different models based on request attributes, evaluated in priority order. Routing Rules are CEL-based conditions that intercept requests to the [**AI Gateway**](/docs/ai-gateway/using-the-router) and redirect them to a different model when matched. Rules are evaluated in priority order and the first matching rule wins. No further rules are evaluated after a match. ## Use cases Routing rules are most useful when traffic needs to be redirected or distributed at the gateway level based on request attributes, without modifying any calling application. Intercepts support bot requests addressed to one model and silently redirects them to a cheaper one, so the calling application needs no changes to benefit from the cost saving. Two rules at the same priority split document traffic by complexity: simple documents go to a lighter model, complex ones go to a more capable model, all based on metadata attached to each request. Matches requests that include a file attachment and routes them through an ordered list of models that all support native file input, so the request succeeds even if the first model is unavailable. A low-priority catch-all rule that redirects traffic to an alternative set of models whenever primary providers are unavailable, keeping requests flowing without manual intervention. Distributes requests in round-robin mode across multiple endpoints serving the same model, balancing inference load across providers without any changes required on the caller side. ## How routing rules work When a request arrives at the AI Gateway, the **AI Gateway** evaluates all active routing rules in ascending priority order. The first rule whose CEL expression matches the request determines the target model. If no rule matches, the model from the original request payload is used. **Example:** A request arrives with `model: "openai/gpt-5.6-sol"`. A routing rule with condition `model.contains("gpt-5.6-sol")` and target `openai/gpt-5.4-mini` is the highest-priority matching rule. The **AI Gateway** redirects the request to `gpt-5.4-mini`, regardless of what the caller specified. In CEL expressions, `model` refers to the model value from the request payload. To distribute traffic across providers at the request level without organization-wide rules, use the `load_balancer` parameter directly in your API calls. ## Visibility * Visible to workspace administrators only. ## Creating a routing rule From the **Routing Rules** list, click Add New Rule. A panel opens on the right with the following fields. Create Routing Rule form top showing the General fields for rule name, description, priority, and enable rule, a Load Balancer section with the Latency strategy and three target models, and a Cache section with an Enable cache toggle and a 1 hour time-to-live. Create Routing Rule form bottom showing a Plugins section with PII Redaction attached, and a Conditions section with a rule builder condition on the ENVIRONMENT header and the generated CEL expression. ### General | Field | Description | | --------------- | ------------------------------------------------------------------------------------------------------------- | | **Rule Name** | A display name for the routing rule. | | **Description** | Optional context for administrators. | | **Enable Rule** | Toggle to activate or deactivate the rule. When enabled, the rule is active and applied to matching requests. | ### Providers and traffic weight Optional. Defines the target model or models to route matching requests to. Leave this section empty to build a rule that exists only to apply a [plugin](#plugins) or a [cache](#cache) to matched traffic, with no model routing at all. **Models** sets the distribution strategy for the target: * **Fallback**: Route to the primary model. If it fails, try the next in the list. * **Latency**: Route to the model with the lowest recently observed latency. See [Latency-based routing](/docs/ai-gateway/features/load-balancing#latency-based-routing) for the full selection algorithm. * **Weighted**: Split traffic across models by percentage weights. * **Round Robin**: Rotate evenly across all configured models. Choose **Latency** when minimizing response time matters more than a fixed traffic split. Choose **Weighted** or **Round Robin** when the split itself, such as cost control or A/B testing, is the goal. Click Add to add a target model. When a request matches this routing rule, its **Providers and traffic weight** configuration completely replaces the request's own `model`, `load_balancer`, and `fallbacks` values; they are never merged. A request-level [`load_balancer`](/docs/ai-gateway/features/load-balancing) parameter has no effect once a matching routing rule with configured models applies. ### Cache Optional. Give the rule its own response cache for matching requests. Exact-match requests reuse a cached response instead of hitting a model. Leaving this section disabled adds no cache of its own, so matched requests keep using their request-level [`cache`](/docs/ai-gateway/features/cache) setting. Set **Enable cache** to turn the cache on, then choose **Time to live** to control how long matching responses stay cached, from 5 minutes up to 3 days. The default is 1 hour. When a rule has cache enabled, a matching request uses the rule's cache configuration instead of the request's own [`cache`](/docs/ai-gateway/features/cache) parameter; the two are not merged. See [LLM response caching](/docs/ai-gateway/features/cache) for how caching works. ### Plugins Optional. Attach plugins that run on traffic matching this rule. Plugins can transform the request, the response, or stored traces. Click Add Plugin to attach one of the following: | Plugin | What it does | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **PII Redaction** | Replaces personally identifiable information with placeholders before the provider sees it, then restores the original values in the response. Configure the entities to mask, the detection threshold, and the failure behavior. See [PII Redaction](/docs/ai-gateway/features/plugins/pii-redaction). | | **Response Healing** | Repairs malformed JSON in model output before the response reaches the caller. Takes no configuration. See [Response Healing](/docs/ai-gateway/features/plugins/response-healing). | | **Trace Scrubbing** | Masks request and response fields in stored traces for matched traffic. Configure which fields to scrub. | ### Priority A numeric value that sets the evaluation order for this rule. Rules are evaluated in ascending order: priority `0` is evaluated before priority `10`. The first matching rule wins. ### Rule Builder The Rule Builder constructs the CEL expression that determines whether this rule applies to a given request. Conditions are built from values present in the request headers and body. Clicking Add opens a dropdown with the following condition types: | Condition | Description | | ------------ | ---------------------------------------------------- | | **Header** | Match on a request header name and value. | | **Model** | Match on the model specified in the request payload. | | **Identity** | Match on the identity making the request. | | **Metadata** | Match on metadata attached to the request. | | **Project** | Match on the project scope of the request. | You can also click **Add group** to nest conditions into a logical group. Multiple conditions within a group are joined with `and`. Groups themselves can be joined with either `and` or `or`. Select the operator between groups to control how they combine. Each condition can be removed with . The generated CEL expression is shown read-only in the **CEL Expression Preview** below the builder. # Anthropic Messages API Source: https://docs.orq.ai/docs/ai-gateway/features/anthropic-messages-api Use the Anthropic SDK unmodified against the Orq.ai AI Gateway with prompt caching on Claude. **Use Cases** Keep an existing Anthropic SDK codebase and point it at **Orq.ai** with a single `base_url` change; streaming, tool use, and multimodal input keep working unchanged. Mark the system prompt or reference documents with `cache_control` so repeat calls and follow-up turns read them from cache instead of paying full price. Change the `model` value to any `provider/model_id` in the **AI Gateway** catalog, from Claude to OpenAI and other providers, without swapping SDKs. Fallbacks, budgets, guardrails, and traces apply to every Anthropic SDK call routed through the gateway. *** ## Overview The **AI Gateway** is a routing layer that proxies one API to 500+ models across providers, adding fallbacks, budgets, guardrails, and traces; see the [quick start](/docs/ai-gateway/get-started/introduction). It exposes an Anthropic-compatible endpoint at `https://my.orq.ai/v3/anthropic`. The Anthropic SDK appends `/v1/messages` to the base URL automatically, so existing Anthropic code runs against **Orq.ai** with no other changes. Requests authenticate with an **Orq.ai** API key and run through the same pipeline as the [OpenAI-compatible API](/docs/ai-gateway/features/openai-compatible-api), so fallbacks, budgets, guardrails, and traces all apply. Prompt caching on Claude models is opt-in via `cache_control` breakpoints, exactly as in the native Anthropic API. ## Quick Start **Before you start**: complete the [AI Gateway quick start](/docs/ai-gateway/get-started/introduction) once. It walks through creating an account at [my.orq.ai](https://my.orq.ai), connecting a provider (BYOK) with an Anthropic API key, and creating an **Orq.ai** API key. Then set the key in the environment: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export ORQ_API_KEY="your-api-key" ``` Set the base URL to `https://my.orq.ai/v3/anthropic` and send a messages request with an **Orq.ai** API key. cURL requests use the full path shown below; SDKs set `baseURL` to `https://my.orq.ai/v3/anthropic` and the client appends `/v1/messages` automatically. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/anthropic/v1/messages \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-5", "max_tokens": 1024, "messages": [ { "role": "user", "content": "Explain the AI Gateway in one paragraph." } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/anthropic", }); const message = await client.messages.create({ model: "anthropic/claude-sonnet-5", max_tokens: 1024, messages: [ { role: "user", content: "Explain the AI Gateway in one paragraph." }, ], }); console.log(message.content[0].text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import anthropic import os client = anthropic.Anthropic( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/anthropic", ) message = client.messages.create( model="anthropic/claude-sonnet-5", max_tokens=1024, messages=[{"role": "user", "content": "Explain the AI Gateway in one paragraph."}], ) print(message.content[0].text) ``` ## Supported Endpoints All routes are relative to the base URL `https://my.orq.ai/v3/anthropic` and mirror the Anthropic Messages API request and response formats. | Endpoint | Description | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `POST /v1/messages` | Create a message. Supports [streaming](/docs/ai-gateway/features/streaming), [tool use](/docs/ai-gateway/features/tool-calling), and [multimodal input](/docs/ai-gateway/features/multimodal). | | `POST /v1/messages/count_tokens` | Count input tokens for a request without calling the model. Returns `{"input_tokens": }`. Returns `{"input_tokens": 0}` when token counting is not supported for the resolved model. | | `GET /v1/models` | List models available through the **AI Gateway**. | | `GET /v1/models/{model_id}` | Get details for a single model. | `count_tokens` returns `{"input_tokens": 0}` when the resolved model does not support token counting, which is indistinguishable from a real zero-token result. If the count is required, verify the model supports counting before relying on it. ## Authentication Authenticate with the **Orq.ai** API key in either header: * `Authorization: Bearer $ORQ_API_KEY` * `x-api-key: $ORQ_API_KEY` (the header the Anthropic SDK sends by default) The **Anthropic SDK** works unmodified: set `ANTHROPIC_API_KEY` to an **Orq.ai** key and `ANTHROPIC_BASE_URL` to `https://my.orq.ai/v3/anthropic`. Alternatively, pass the constructor parameters shown in the Quick Start (`apiKey`/`baseURL` in TypeScript, `api_key`/`base_url` in Python). To learn more about **Orq.ai** API keys, see [API Keys](/docs/ai-gateway/configuration/api-keys). ## Model Naming Use the `provider/model_id` format from the **AI Gateway** catalog: * `anthropic/claude-sonnet-5` for Claude models. * Any other provider prefix, for example `openai/gpt-5.6-sol`, routed through the same Anthropic-compatible endpoint. `GET /v1/models` returns some models as native IDs without the provider prefix (for example `claude-sonnet-4-6`). Those IDs also resolve on this endpoint; the catalog `provider/model_id` form is unambiguous. The same catalog applies to every **AI Gateway** API, including `/v3/router` and the Responses API. Browse every available model in [Supported Models](/docs/ai-gateway/supported-models). ## Gateway Features Because requests run through the same pipeline as the router, the following **AI Gateway** features apply to Anthropic SDK calls: * [Smart Router](/docs/ai-gateway/smart-router) * [Load Balancing and Fallbacks](/docs/ai-gateway/features/load-balancing) * [Retries](/docs/ai-gateway/features/retries) * [Budgets](/docs/ai-gateway/budgets) * [Guardrails](/docs/ai-gateway/configuration/guardrails) * [Traces](/docs/ai-gateway/traces) * [Prompt caching](/docs/ai-gateway/features/prompt-caching) * [Response caching](/docs/ai-gateway/features/cache) ### Prompt Caching Prompt caching is opt-in: add a `cache_control` object to a content block to mark a cache breakpoint, the end of a cacheable prefix. Breakpoints pass through unchanged on this endpoint, including multi-turn and streaming requests. Supported `cache_control` values, TTLs, block types, minimum token thresholds, and usage reporting. Cache writes and reads appear in the response `usage` as `cache_creation_input_tokens` (write) and `cache_read_input_tokens` (read), the native Anthropic shape. `cache_control` on a non-Anthropic model is ignored, not rejected. #### Multi-turn example Mark the system prompt with a breakpoint, then send a follow-up turn with the same prefix to read from cache. The system text below is abbreviated; use a prefix above the [provider minimum token threshold](/docs/ai-gateway/features/prompt-caching#minimum-token-thresholds) for the cache read to appear. The TypeScript and Python tabs reuse the client from the Quick Start. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} # First call: writes the prefix to cache curl -X POST https://my.orq.ai/v3/anthropic/v1/messages \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-5", "max_tokens": 1024, "system": [ { "type": "text", "text": "You are a senior legal assistant. The following is our complete contract template library...", "cache_control": { "type": "ephemeral", "ttl": "1h" } } ], "messages": [ { "role": "user", "content": "Summarize clause 7 of the NDA template." } ] }' # Second call with the same prefix: reads from cache curl -X POST https://my.orq.ai/v3/anthropic/v1/messages \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-5", "max_tokens": 1024, "system": [ { "type": "text", "text": "You are a senior legal assistant. The following is our complete contract template library...", "cache_control": { "type": "ephemeral", "ttl": "1h" } } ], "messages": [ { "role": "user", "content": "Summarize clause 8 of the NDA template." } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Uses the client from the Quick Start above const system = [ { type: "text", text: "You are a senior legal assistant. The following is our complete contract template library...", cache_control: { type: "ephemeral", ttl: "1h" }, }, ]; // First call: writes the prefix to cache await client.messages.create({ model: "anthropic/claude-sonnet-5", max_tokens: 1024, system, messages: [ { role: "user", content: "Summarize clause 7 of the NDA template." }, ], }); // Second call with the same prefix: reads from cache const message = await client.messages.create({ model: "anthropic/claude-sonnet-5", max_tokens: 1024, system, messages: [ { role: "user", content: "Summarize clause 8 of the NDA template." }, ], }); console.log(message.usage.cache_read_input_tokens); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} # Uses the client from the Quick Start above system = [ { "type": "text", "text": "You are a senior legal assistant. The following is our complete contract template library...", "cache_control": {"type": "ephemeral", "ttl": "1h"}, } ] # First call: writes the prefix to cache client.messages.create( model="anthropic/claude-sonnet-5", max_tokens=1024, system=system, messages=[{"role": "user", "content": "Summarize clause 7 of the NDA template."}], ) # Second call with the same prefix: reads from cache message = client.messages.create( model="anthropic/claude-sonnet-5", max_tokens=1024, system=system, messages=[{"role": "user", "content": "Summarize clause 8 of the NDA template."}], ) print(message.usage.cache_read_input_tokens) ``` The second call sends the identical system prefix with a new user turn. When the marked prefix is above the provider minimum token threshold, the response reports `cache_read_input_tokens` greater than zero instead of paying full price for the system prompt. #### Manual vs Automatic Prompt Caching How prompt caching is enabled depends on the endpoint and the provider: | Endpoint | Provider | How caching is enabled | | -------------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | Anthropic-compatible (`/v3/anthropic`) | Claude | Manual `cache_control` breakpoints on content blocks. No automatic mode. | | Responses API (`/v3/router/responses`) | Claude | Manual `cache_control` on the system prompt by default. Use the top-level `cache_control` parameter only when continuing with `previous_response_id`. | | Responses API (`/v3/router/responses`) | OpenAI, Google Gemini 2.0+ | Fully automatic. No request changes needed. | Use manual breakpoints when the request has a long stable prefix and the cache boundary matters. The Responses API's top-level `cache_control` marks the last cacheable block automatically; it does not allow multiple breakpoints, and it is the form to use with `previous_response_id`. See [Multi-turn conversations](/docs/ai-gateway/features/prompt-caching#multi-turn-conversations). OpenAI and Gemini 2.0+ cache automatically, and providers that cache automatically ignore `cache_control`, so the two approaches do not conflict. For the full comparison, see [Prompt caching](/docs/ai-gateway/features/prompt-caching). # Background Responses Source: https://docs.orq.ai/docs/ai-gateway/features/background-responses Run a Responses API request asynchronously and retrieve the result by response ID. Use background execution when a **Responses API** request should return before model execution completes. Set `background` to `true` on `POST /v3/router/responses`. The **AI Gateway** stores the response, returns its ID with `status: "queued"`, and processes it asynchronously. Poll `GET /v3/router/responses/{response_id}` with the same ID until the response reaches a terminal status. See the [Responses API reference](/reference/responses/create-response) for the complete request and response schema. ## Quick start The following examples submit a background response, then poll until processing finishes. Set `store` to `true`; background responses require stored responses. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} response=$(curl -sS -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4o-mini", "input": "Summarize the background response workflow in one sentence.", "background": true, "store": true }') response_id=$(printf '%s' "$response" | jq -r '.id') status=$(printf '%s' "$response" | jq -r '.status') while [ "$status" = "queued" ] || [ "$status" = "in_progress" ]; do sleep 1 response=$(curl -sS "https://my.orq.ai/v3/router/responses/$response_id" \ -H "Authorization: Bearer $ORQ_API_KEY") status=$(printf '%s' "$response" | jq -r '.status') done printf '%s\n' "$response" ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const headers = { Authorization: `Bearer ${process.env['ORQ_API_KEY']}`, 'Content-Type': 'application/json', }; let response = await fetch('https://my.orq.ai/v3/router/responses', { method: 'POST', headers, body: JSON.stringify({ model: 'openai/gpt-4o-mini', input: 'Summarize the background response workflow in one sentence.', background: true, store: true, }), }).then((result) => result.json()); while (response.status === 'queued' || response.status === 'in_progress') { await new Promise((resolve) => setTimeout(resolve, 1000)); response = await fetch( `https://my.orq.ai/v3/router/responses/${response.id}`, { headers }, ).then((result) => result.json()); } console.log(response); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os import time import requests headers = { 'Authorization': f"Bearer {os.getenv('ORQ_API_KEY', '')}", 'Content-Type': 'application/json', } response = requests.post( 'https://my.orq.ai/v3/router/responses', headers=headers, json={ 'model': 'openai/gpt-4o-mini', 'input': 'Summarize the background response workflow in one sentence.', 'background': True, 'store': True, }, ).json() while response['status'] in ('queued', 'in_progress'): time.sleep(1) response = requests.get( f"https://my.orq.ai/v3/router/responses/{response['id']}", headers=headers, ).json() print(response) ``` ## Response lifecycle The create request returns the response object with a stable `id`. Retrieve that same object while it progresses through the following statuses: | Status | Meaning | | ------------- | --------------------------------------------------------------------------- | | `queued` | The request was accepted and is waiting for background execution. | | `in_progress` | Background execution is running. | | `completed` | The response contains the completed output. | | `failed` | Execution stopped with an error in the `error` field. | | `incomplete` | Execution ended without a completed response; inspect `incomplete_details`. | Completed responses include `output` and `completed_at`. Failed responses retain the response ID so the error can be retrieved with the same GET request. ## Configuration and limitations | Field | Requirement | | ------------ | --------------------------------------------------------------------------------------------- | | `background` | Set to `true` to enqueue the response and return immediately. | | `store` | Omit this field or set it to `true`. `store: false` is rejected for background responses. | | `stream` | Omit this field or set it to `false`. Background execution cannot be combined with streaming. | Background execution uses the standard Responses API model, input, tools, and response schema. The asynchronous behavior changes when the result becomes available, not the shape of the final response. # LLM response caching Source: https://docs.orq.ai/docs/ai-gateway/features/cache Cache identical LLM requests to reduce latency by 95% and cut API costs. Configure TTL, exact match caching, and optimize response times for repeated queries. **Use Cases** * Eliminating redundant costs on repeated identical queries (FAQs, product lookups). * Speeding up development and test loops by caching fixture requests. * Serving the same prompt to many concurrent users without paying per call. * Reducing tail latency on frequently-called endpoints. *** ## Quick Start Cache identical requests to reduce latency by \~95% and save costs. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [{ "role": "user", "content": "Explain renewable energy" }], "cache": { "type": "exact_match", "ttl": 3600 } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Explain renewable energy", cache: { type: "exact_match", ttl: 3600, }, }); console.log(response.output_text); ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Explain renewable energy" }], cache: { type: "exact_match", ttl: 3600, }, }); console.log(response.choices[0].message.content); ``` ## Configuration | Parameter | Type | Required | Description | Example | | --------- | --------------- | -------- | -------------------------------------------------------- | --------------- | | `type` | `"exact_match"` | Yes | Only supported cache type | `"exact_match"` | | `ttl` | number | No | Cache expiration in seconds (default: 1800, max: 259200) | `3600` | **Cache Key**: Generated from model + input + all parameters. Identical requests share the same key. ## TTL Recommendations | Use Case | TTL (seconds) | Reason | | ------------------- | -------------- | ----------------------- | | FAQ responses | `86400` (24h) | Static content | | Content generation | `3600` (1h) | Moderate freshness | | Development/testing | `300` (5min) | Rapid iteration | | Data analysis | `1800` (30min) | Balance speed/freshness | ## Code examples The examples below use the Chat Completions endpoint. The same `cache` parameter applies to the Responses API: replace `chat.completions.create(...)` with `responses.create(...)`. ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [ { "role": "user", "content": "Explain the benefits of renewable energy for businesses" } ], "cache": { "type": "exact_match", "ttl": 3600 } }' ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: "Explain the benefits of renewable energy for businesses", }, ], cache: { type: "exact_match", ttl: 3600, }, }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router" ) response = client.chat.completions.create( model="openai/gpt-5.6-sol", messages=[ { "role": "user", "content": "Explain the benefits of renewable energy for businesses" } ], extra_body={ "cache": { "type": "exact_match", "ttl": 3600 } } ) ``` ## Troubleshooting **Low cache hit rate** * Ensure identical parameters (temperature, max\_tokens, etc.). * Check TTL isn't too short for your use case. * Verify requests are truly identical (case-sensitive). **Cache not working** * Confirm `type: "exact_match"` is specified. * Check response headers for cache status. **Performance issues** * Use shorter TTL for dynamic content. * Consider cache warming for predictable requests. * Monitor cache hit/miss ratios. ## Limitations * **Exact match only**: Any parameter change creates new cache key. * **Case sensitive**: "Hello" and "hello" are different cache keys. * **No semantic matching**: Similar but not identical requests won't match. * **Storage limits**: Very large responses consume more cache space. * **TTL constraints**: Minimum 1 second, maximum 259200 seconds (3 days). ## Best Practices * Set TTL based on content freshness requirements. * Use cache for repeated, deterministic requests. * Monitor cache hit rates to optimize TTL values. * Avoid caching personalized or time-sensitive content. * Test cache behavior in development before production. # Embeddings Source: https://docs.orq.ai/docs/ai-gateway/features/embeddings Create vector embeddings through the AI Gateway with any supported embedding model. Generate embeddings for semantic search, clustering, and RAG ingestion. **Use Cases** * Semantic search and retrieval over a collection of documents. * Clustering, classification, and anomaly detection on text. * Ingesting vectors into a custom or third-party vector database. * Building RAG pipelines where retrieval happens outside the gateway. *** ## Overview The **AI Gateway** exposes `POST /embeddings` on the [OpenAI-compatible API](/docs/ai-gateway/features/openai-compatible-api) base URL (`https://my.orq.ai/v3/router`). Send text and receive vector embeddings from any enabled embedding model, with the same request and response format as the OpenAI Embeddings API. Fallbacks, budgets, caching, and observability apply to embedding calls exactly as they do to chat completions. ## Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/embeddings \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/text-embedding-3-small", "input": "The food was delicious" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const embedding = await client.embeddings.create({ model: "openai/text-embedding-3-small", input: "The food was delicious", }); console.log(embedding.data[0].embedding); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) embedding = client.embeddings.create( model="openai/text-embedding-3-small", input="The food was delicious", ) print(embedding.data[0].embedding) ``` ## Providers and Models Providers that support embeddings include AWS Bedrock, Azure, Cohere, Vertex AI, Google AI, Jina AI, Mistral, Nebius, OpenAI, Scaleway, Tencent, and TensorX. See [Supported Models](/docs/ai-gateway/supported-models) for the full embedding model catalog. ## Model Selection and Dimensions Choose a model based on which languages it supports, cost, and output quality. For content in multiple languages, use a multilingual model such as `cohere/embed-multilingual-v3.0` or `jina/jina-embeddings-v3`. For English-only content, `openai/text-embedding-3-small` is smaller and cheaper. Pass `dimensions` to request a specific number of output dimensions: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "model": "openai/text-embedding-3-small", "input": "The food was delicious", "dimensions": 256 } ``` Models that support configurable output sizes (for example `openai/text-embedding-3-small` and `openai/text-embedding-3-large`) return vectors of the requested size, which reduces storage and compute cost with a small loss in retrieval quality. Models with a fixed output size return vectors of their fixed dimension. Set `encoding_format` to `base64` to receive embeddings as base64-encoded strings instead of JSON float arrays, which makes the response smaller: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "model": "openai/text-embedding-3-small", "input": "The food was delicious", "encoding_format": "base64" } ``` ## Batching and Token Usage The `input` field accepts a single string, an array of strings, or token arrays. Batch related texts in one request instead of sending one request per text. One round trip is faster than many: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/embeddings \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/text-embedding-3-small", "input": [ "The food was delicious", "And the waiter was friendly" ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const batch = await client.embeddings.create({ model: "openai/text-embedding-3-small", input: [ "The food was delicious", "And the waiter was friendly", ], }); console.log(batch.data.map((item) => item.embedding.length)); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) batch = client.embeddings.create( model="openai/text-embedding-3-small", input=[ "The food was delicious", "And the waiter was friendly", ], ) print([len(item.embedding) for item in batch.data]) ``` The response returns one embedding per input in the `data` array, ordered to match the input. Token usage follows the OpenAI format: `usage.prompt_tokens` reports the input tokens consumed and `usage.total_tokens` the total for the request. ## Embeddings and Knowledge Bases [Knowledge Bases](/docs/ai-gateway/features/knowledge-bases) handle embedding and retrieval internally: chunk the source documents, embed them with a configured [embedding model](/docs/ai-studio/ai-engineering/knowledge-bases#embedding-models), and inject the retrieved context into the model call. Prefer a Knowledge Base when the goal is RAG inside the gateway, because it handles ingestion and retrieval automatically. Call the embeddings endpoint directly when the pipeline needs control that a Knowledge Base does not offer: * Storing vectors in an external vector database such as [Pinecone or a custom vector DB](/docs/ai-studio/cookbooks/integrations-tooling/using-thirdparty-vectordbs-with-orq). * Using custom chunking, a custom embedding model, or embeddings for non-retrieval tasks such as clustering and classification. * Running retrieval outside the gateway while keeping generation inside it. ## Fallbacks, Caching, Budgets, and Observability Embedding calls support the same gateway controls as chat completions: * **Fallbacks**: pass an array of `fallbacks` to route to backup models when the primary model fails. See [Fallbacks](/docs/ai-gateway/features/retries#fallbacks). * **Caching**: identical embedding requests can be served from cache. See [Cache](/docs/ai-gateway/features/cache). * **Budgets**: embedding traffic counts against [Budgets](/docs/ai-gateway/budgets), with limits applied per workspace, API key, or provider. * **Observability**: embedding calls appear in [Traces](/docs/ai-gateway/traces) with model, token usage, and latency. Pass the top-level `name` field, for example `"name": "semantic-search-embed"`, to label the call on the trace. # Sending files to models Source: https://docs.orq.ai/docs/ai-gateway/features/files Which models accept files, images, PDFs, and audio through the AI Gateway, how to shape each content part, and when to send a URL or base64. **Use Cases** * Choosing a model that can read the file type being sent. * Sending an image or a document in a model request without uploading it first. * Deciding between a public URL and base64 for a given provider. *** This page covers which models accept files and how to shape the request. Complete examples per modality, plus image generation and audio, are owned by [Image, PDF, and audio: multimodal inputs and generation](/docs/ai-gateway/features/multimodal). Files reach a model **inline**, as a content part in the request. There is no upload step: pass a public URL or a base64 data URI, and the **AI Gateway** converts it to the shape the target provider expects. Two things trip up most first requests: * **Image support and PDF support are separate capabilities.** A model that reads images very often cannot read a PDF. * **Images and documents use different content parts**, with different required fields. Looking to store a document for reuse across knowledge bases or batch jobs? That is the [Files API](/docs/ai-studio/ai-engineering/files), a separate system. Files uploaded there cannot be referenced by ID in a model request. ## Which models accept what Of the models in the **AI Gateway** catalog, image input, PDF input, and audio input have meaningfully different acceptance rates — and the sets are not nested: **image support does not imply PDF support.** | Model | Image | PDF | Audio | | ----------------------------------------------------- | :---: | :-: | :---: | | `openai/gpt-5.6-sol` | ✓ | ✓ | ✗ | | `openai/gpt-5.4-mini` | ✓ | ✓ | ✗ | | `anthropic/claude-opus-5` | ✓ | ✓ | ✗ | | `anthropic/claude-sonnet-5` | ✓ | ✓ | ✗ | | `google/gemini-3.7-flash` | ✓ | ✓ | ✓ | | `google/gemini-3.1-pro-preview` | ✓ | ✓ | ✓ | | `xai/grok-4.6` | ✓ | ✗ | ✗ | | `mistral/mistral-medium-3.5` | ✓ | ✗ | ✗ | | `alibaba/qwen3.6-27b` | ✓ | ✗ | ✓ | | `google/meta/llama-4-maverick-17b-128e-instruct-maas` | ✓ | ✗ | ✗ | | `aws/us.amazon.nova-2-lite-v1:0` | ✓ | ✗ | ✗ | **"Vision model" does not imply "reads PDFs".** Whole families accept images without accepting PDFs, including Mistral (Pixtral, Magistral, Ministral, Mistral Medium), Llama 3.2 and Llama 4, Qwen 3.5 and 3.6, Grok 4.5 and 4.6, and Amazon Nova. Capability also varies **within** a family, so check the specific model rather than the family. The modality tabs on the [Models page](/docs/ai-gateway/using-the-router) filter by model *type* (chat, image generation, embedding), not by accepted input. For a model that is not in the table above, check the provider's documentation before sending a PDF. ## Sending an image vs sending a document Images and documents are different content parts. The table shows the shape; for complete runnable examples in each language, see [Image, PDF, and audio: multimodal inputs and generation](/docs/ai-gateway/features/multimodal). | | Image | Document | | --------------------- | --------------------- | ------------ | | Responses API type | `input_image` | `input_file` | | Chat Completions type | `image_url` | `file` | | Payload field | `image_url` | `file_data` | | `filename` | not used | **required** | | `detail` | `low`, `high`, `auto` | not used | ```json JSON (Responses API) theme={"theme":{"light":"github-light","dark":"github-dark"}} { "role": "user", "content": [ { "type": "input_text", "text": "Compare these." }, { "type": "input_image", "image_url": "https://example.com/chart.png", "detail": "high" }, { "type": "input_file", "filename": "report.pdf", "file_data": "data:application/pdf;base64," } ] } ``` ```json JSON (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} { "role": "user", "content": [ { "type": "text", "text": "Compare these." }, { "type": "image_url", "image_url": { "url": "https://example.com/chart.png", "detail": "high" } }, { "type": "file", "file": { "filename": "report.pdf", "file_data": "data:application/pdf;base64," } } ] } ``` `input_file` is not PDF-only. Anthropic models also accept CSV, plain text, JSON, Markdown, XML, and RTF as documents, and AWS Bedrock additionally accepts DOC, DOCX, XLS, XLSX, and HTML. Set `filename` with the correct extension so the type is detected. Bedrock rewrites document names to alphanumerics, spaces, hyphens, and brackets, truncated to 64 characters. ## URL or base64? A base64 data URI works with every provider and is the safest default. Public URLs are handled differently upstream, and the **AI Gateway** absorbs most of the difference: when a provider cannot fetch a URL itself, the gateway downloads the file and inlines it before forwarding the request. | Provider | Image URL | Document URL | | ------------------ | ---------------------------------- | ---------------------------------- | | OpenAI | Native | Not forwarded: send base64 | | Anthropic | Native | Native | | AWS Bedrock | Fetched and inlined by the gateway | Base64 only | | Google AI (Gemini) | Fetched and inlined by the gateway | Fetched and inlined by the gateway | | Vertex AI | Native, including `gs://` | Native, including `gs://` | Two provider behaviours are worth knowing: **Gemini handles files natively, and the gateway uses that.** A `fileUri` from the Gemini Files API is passed straight through rather than downloaded. Base64 content larger than 4 MB is uploaded to the Gemini Files API automatically and sent as a `fileUri`, which keeps large documents out of the request body. Only arbitrary public URLs are fetched and inlined, because Gemini rejects unknown URIs with `Invalid or unsupported file uri`. On Vertex AI, `gs://` and public URLs are both resolved server side and pass through untouched. **A document `file_url` does not reach OpenAI.** On the message path the gateway forwards `file_data`, `file_id`, and `filename`, so a PDF supplied as `file_url` arrives with no source and is ignored. Send the document as a base64 data URI in `file_data`, or reference an OpenAI `file_id`. Use base64 when the file is not reachable from the public internet, when the URL requires authentication, or when byte-identical input is needed across a fallback chain. Use a URL to keep request bodies small and to benefit from provider-side caching. A content part a provider cannot accept may be **dropped rather than rejected**: the request succeeds, and the model answers without ever having seen the file. If a response ignores the attachment, confirm the model accepts that input type and that the transport is supported before debugging the prompt. ## Related * [Image, PDF, and audio: multimodal inputs and generation](/docs/ai-gateway/features/multimodal), full examples for image, PDF, and audio, plus image generation and speech * [Files API](/docs/ai-studio/ai-engineering/files), upload and manage stored documents in **AI Studio** * [Supported Models](/docs/ai-gateway/supported-models), the model catalog by endpoint * [Run Agents: Attach Files](/docs/ai-studio/ai-engineering/run-agents#attach-files), sending files to a managed agent # Dynamic inputs for runtime configuration Source: https://docs.orq.ai/docs/ai-gateway/features/inputs Pass dynamic inputs to LLM prompts at runtime. Configure variables, context, and parameters through the AI Gateway for flexible prompt execution. **Use Cases** * Personalizing prompts with user-specific data (name, account tier, history) at runtime. * Reusing a single prompt template across many contexts without duplicating it. * Separating prompt logic from runtime data for cleaner, testable code. * Injecting dynamic content (current date, retrieved chunks) without string concatenation. *** Replace variables in prompt messages using `{{variableName}}` syntax for dynamic content injection. ## Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Hello {{customer_name}}, your {{product_name}} subscription expires soon.", "variables": { "customer_name": "John Smith", "product_name": "Premium Plan" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Hello {{customer_name}}, your {{product_name}} subscription expires soon.", variables: { customer_name: "John Smith", product_name: "Premium Plan", }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.6-sol", input="Hello {{customer_name}}, your {{product_name}} subscription expires soon.", extra_body={ "variables": { "customer_name": "John Smith", "product_name": "Premium Plan", } }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Hello {{customer_name}}, your {{product_name}} subscription expires soon.", }], variables: { customer_name: "John Smith", product_name: "Premium Plan", }, }); ``` **Result**: `"Hello John Smith, your Premium Plan subscription expires soon."` ## Configuration | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ---------------------------------------------------------- | | `variables` | object | No | Key-value pairs to replace `{{key}}` variables in messages | **Variable Format**: `{{variableName}}` (case-sensitive, alphanumeric + underscore) ## Use Cases | Scenario | Variables | Example Input | | --------------------------- | ------------------------------------- | ---------------------------------------------------- | | **Customer Support** | `{{customer_name}}`, `{{issue_type}}` | `{customer_name: "Alice", issue_type: "billing"}` | | **Product Recommendations** | `{{user_preferences}}`, `{{budget}}` | `{user_preferences: "eco-friendly", budget: "$500"}` | | **Onboarding** | `{{user_name}}`, `{{plan_type}}` | `{user_name: "Bob", plan_type: "Enterprise"}` | | **Content Generation** | `{{topic}}`, `{{audience}}` | `{topic: "AI trends", audience: "developers"}` | ## Implementation Examples ### Customer Support Automation ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const supportTicket = { customerName: "Sarah Johnson", issueType: "billing", accountType: "premium", ticketId: "TICKET-001" }; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "system", content: "You are a helpful customer support agent for {{company_name}}." }, { role: "user", content: "Hi, I'm {{customer_name}} and I have a {{issue_type}} issue with my {{account_type}} account. Ticket: {{ticket_id}}" }], variables: { company_name: "Acme Corp", customer_name: supportTicket.customerName, issue_type: supportTicket.issueType, account_type: supportTicket.accountType, ticket_id: supportTicket.ticketId }, }); ``` ### Personalized Email Generation ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const emailTemplate = { recipient: "Marketing Team", campaign: "Q4 Product Launch", metrics: "25% increase in engagement" }; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: `Create a performance report email for {{recipient}} about the {{campaign}} campaign. Highlight that we achieved {{metrics}} and include actionable next steps.` }], variables: { recipient: emailTemplate.recipient, campaign: emailTemplate.campaign, metrics: emailTemplate.metrics }, }); ``` ### Multi-Language Support ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Generate a {{content_type}} about {{topic}} for {{target_audience}}", "variables": { "content_type": "blog post", "topic": "sustainable technology", "target_audience": "enterprise decision makers" } }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [{ "role": "user", "content": "Generate a {{content_type}} about {{topic}} for {{target_audience}}" }], "variables": { "content_type": "blog post", "topic": "sustainable technology", "target_audience": "enterprise decision makers" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Generate a {{content_type}} about {{topic}} for {{target_audience}}", variables: { content_type: "blog post", topic: "sustainable technology", target_audience: "enterprise decision makers", }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.6-sol", input="Generate a {{content_type}} about {{topic}} for {{target_audience}}", extra_body={ "variables": { "content_type": "blog post", "topic": "sustainable technology", "target_audience": "enterprise decision makers", } }, ) print(response.output_text) ``` ## Advanced Patterns ### Dynamic Template Loading ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); interface TemplateConfig { template: string; requiredInputs: string[]; defaultInputs?: Record; } const templates: Record = { welcome: { template: "Welcome {{user_name}}! Your {{plan_type}} account is now active.", requiredInputs: ["user_name", "plan_type"] }, reminder: { template: "Hi {{user_name}}, your {{service}} subscription expires in {{days}} days.", requiredInputs: ["user_name", "service", "days"], defaultInputs: { service: "Premium Plan" } } }; async function generateFromTemplate( templateKey: string, inputs: Record ) { const template = templates[templateKey]; if (!template) { throw new Error(`Template ${templateKey} not found`); } // Validate required inputs const missing = template.requiredInputs.filter(key => !inputs[key]); if (missing.length > 0) { throw new Error(`Missing required inputs: ${missing.join(", ")}`); } // Merge with defaults const finalInputs = { ...template.defaultInputs, ...inputs }; return await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: template.template }], variables: finalInputs, }); } // Usage const response = await generateFromTemplate("welcome", { user_name: "Alice Johnson", plan_type: "Enterprise" }); ``` ### Conditional Content Generation ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); // Generate content based on user tier const generateTieredContent = async (userTier, baseContent, inputs) => { const tierSpecificPrompts = { free: "Keep the response brief and mention premium features are available.", premium: "Provide detailed information and include premium tips.", enterprise: "Include advanced strategies and enterprise-specific recommendations." }; const enhancedInputs = { ...inputs, user_tier: userTier, tier_instruction: tierSpecificPrompts[userTier] }; return await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "system", content: "{{tier_instruction}}" }, { role: "user", content: baseContent }], variables: enhancedInputs, }); }; ``` ### Batch Input Processing ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const batchInputs = [ { customer_name: "John Doe", product: "Analytics Pro", status: "trial_ending" }, { customer_name: "Jane Smith", product: "CRM Plus", status: "payment_failed" }, { customer_name: "Bob Wilson", product: "Marketing Suite", status: "renewal_due" } ]; const template = "Hi {{customer_name}}, your {{product}} account has status: {{status}}. Please take action."; const responses = await Promise.all( batchInputs.map(async (inputs) => { return client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: template }], variables: inputs, }); }) ); ``` ## Input Validation ### Type Safety with TypeScript ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} interface EmailInputs { recipient_name: string; company_name: string; meeting_date: string; meeting_time: string; } interface ProductInputs { product_name: string; price: string; features: string; target_audience: string; } function validateInputs(inputs: T, required: (keyof T)[]): void { const missing = required.filter(key => !inputs[key]); if (missing.length > 0) { throw new Error(`Missing required inputs: ${missing.join(", ")}`); } } // Usage const emailInputs: EmailInputs = { recipient_name: "John Smith", company_name: "Acme Inc", meeting_date: "December 15", meeting_time: "2:00 PM" }; validateInputs(emailInputs, ["recipient_name", "company_name", "meeting_date"]); ``` ### Runtime Validation ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Validate input format and constraints function validateInputValue(key, value) { const validators = { email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), phone: (v) => /^\+?[\d\s-()]+$/.test(v), date: (v) => !isNaN(Date.parse(v)), price: (v) => /^\$?\d+(\.\d{2})?$/.test(v), name: (v) => v.length >= 2 && v.length <= 50 }; const validator = validators[key]; if (validator && !validator(value)) { throw new Error(`Invalid ${key} format: ${value}`); } } // Sanitize inputs function sanitizeInputs(inputs) { const sanitized = {}; for (const [key, value] of Object.entries(inputs)) { // Remove HTML tags and trim whitespace const cleanValue = String(value) .replace(/<[^>]*>/g, '') .trim() .substring(0, 1000); // Limit length validateInputValue(key, cleanValue); sanitized[key] = cleanValue; } return sanitized; } ``` ## Error Handling ### Template Variable Detection ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Extract variables from template function extractVariables(template) { const matches = template.match(/\{\{([^}]+)\}\}/g) || []; return matches.map(match => match.slice(2, -2).trim()); } // Validate all variables have inputs function validateTemplate(template, inputs) { const variables = extractVariables(template); const missing = variables.filter(variable => !inputs.hasOwnProperty(variable)); if (missing.length > 0) { throw new Error(`Missing inputs for variables: ${missing.join(", ")}`); } return true; } // Usage const template = "Hello {{name}}, your {{product}} expires on {{date}}"; const inputs = { name: "John", product: "Premium Plan" }; // Missing 'date' try { validateTemplate(template, inputs); } catch (error) { console.error(error.message); // "Missing inputs for variables: date" } ``` ### Safe Input Substitution ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Preview substitution without making API call function previewSubstitution(template, inputs) { let result = template; // Replace known variables for (const [key, value] of Object.entries(inputs)) { const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g'); result = result.replace(regex, value); } // Highlight missing variables const remaining = result.match(/\{\{[^}]+\}\}/g) || []; return { preview: result, missingVariables: remaining.map(v => v.slice(2, -2)), isComplete: remaining.length === 0 }; } // Usage const preview = previewSubstitution( "Hi {{name}}, your {{product}} status is {{status}}", { name: "Alice", product: "Pro Plan" } ); console.log(preview.preview); // "Hi Alice, your Pro Plan status is {{status}}" console.log(preview.missingVariables); // ["status"] console.log(preview.isComplete); // false ``` ## Performance Optimization ### Input Caching ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Cache frequently used input combinations const inputCache = new Map(); function getCachedInputs(cacheKey, inputGenerator) { if (inputCache.has(cacheKey)) { return inputCache.get(cacheKey); } const inputs = inputGenerator(); inputCache.set(cacheKey, inputs); // Auto-expire cache entries setTimeout(() => inputCache.delete(cacheKey), 5 * 60 * 1000); // 5 minutes return inputs; } // Usage const userId = "user-123"; const user = { name: "Alice", subscription: { tier: "pro" }, lastLogin: new Date() }; const userInputs = getCachedInputs(`user-${userId}`, () => ({ user_name: user.name, user_tier: user.subscription.tier, last_login: user.lastLogin.toDateString() })); ``` ### Template Compilation ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); function extractVariables(template) { const matches = template.match(/\{\{([^}]+)\}\}/g) || []; return matches.map(match => match.slice(2, -2).trim()); } function validateTemplate(template, inputs) { const variables = extractVariables(template); const missing = variables.filter(v => !Object.prototype.hasOwnProperty.call(inputs, v)); if (missing.length > 0) { throw new Error(`Missing inputs for variables: ${missing.join(", ")}`); } return true; } // Pre-compile templates for better performance class TemplateCompiler { constructor() { this.compiledTemplates = new Map(); } compile(templateId, template) { const variables = extractVariables(template); this.compiledTemplates.set(templateId, { template, variables, requiredInputs: variables }); } async execute(templateId, inputs, model = "openai/gpt-5.6-sol") { const compiled = this.compiledTemplates.get(templateId); if (!compiled) { throw new Error(`Template ${templateId} not found`); } validateTemplate(compiled.template, inputs); return await client.chat.completions.create({ model, messages: [{ role: "user", content: compiled.template }], variables: inputs, }); } } // Usage const compiler = new TemplateCompiler(); compiler.compile("welcome", "Welcome {{user_name}} to {{service_name}}!"); const response = await compiler.execute("welcome", { user_name: "John", service_name: "AI Assistant" }); ``` ## Best Practices 1. **Variable Naming**: Use descriptive, snake\_case variable names 2. **Input Validation**: Always validate inputs before sending requests 3. **Template Testing**: Test templates with sample data before production 4. **Security**: Sanitize user inputs to prevent injection attacks 5. **Performance**: Cache frequently used input combinations 6. **Documentation**: Document required inputs for each template ## Troubleshooting **Variables not replaced** * **Cause:** Typo in variable name or syntax. * **Solution:** Check `{{variableName}}` format matches input keys. **Missing content** * **Cause:** Required inputs not provided. * **Solution:** Validate all template variables have corresponding inputs. **Unexpected output** * **Cause:** HTML/special characters in inputs. * **Solution:** Sanitize inputs before sending. **Performance issues** * **Cause:** Large input objects. * **Solution:** Limit input size and cache frequently used values. ## Limitations | Limitation | Description | Workaround | | ---------------------- | ---------------------------------------- | -------------------------------- | | **Variable Syntax** | Only `{{variableName}}` format supported | Use consistent naming convention | | **Nested Objects** | No support for `{{user.name}}` syntax | Flatten object structure | | **Input Size** | Large inputs increase token usage | Keep inputs concise and relevant | | **Special Characters** | Some characters may need escaping | Sanitize inputs appropriately | | **Case Sensitivity** | Variable names are case-sensitive | Use consistent casing throughout | ## Integration Examples ### CMS Integration ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const getCmsClient = () => ({ getTemplate: async (id: string) => ({ template: "Announcing {{product_name}}..." }) }); // replace with your CMS client // Integrate with content management systems const cms = getCmsClient(); const cmsContent = await cms.getTemplate("product-announcement"); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: cmsContent.template }], variables: { product_name: "AI Analytics Pro", release_date: "January 2024", key_features: "Advanced reporting, Real-time insights, Custom dashboards", target_audience: "Enterprise customers" }, }); ``` ### Database Integration ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const database = { users: { findById: async (id: string) => ({ fullName: "Jane Doe", subscription: { type: "pro" } }) }, templates: { findById: async (id: string) => ({ content: "Hello {{user_name}}" }) } }; // replace with your database client const getUserUsageStats = async (userId: string) => ({ requests: 0 }); // replace with your stats lookup const getPersonalizedRecommendations = async (userId: string) => ([]); // replace with your recommendations lookup // Pull dynamic content from database async function generatePersonalizedContent(userId, templateId) { const [user, template] = await Promise.all([ database.users.findById(userId), database.templates.findById(templateId) ]); const inputs = { user_name: user.fullName, account_type: user.subscription.type, usage_stats: await getUserUsageStats(userId), recommendations: await getPersonalizedRecommendations(userId) }; return await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: template.content }], variables: inputs, }); } ``` # Knowledge bases via AI Gateway Source: https://docs.orq.ai/docs/ai-gateway/features/knowledge-bases Integrate knowledge bases through the AI Gateway. Enable RAG retrieval in LLM calls with automatic context injection for enhanced AI responses. **Use Cases** * Grounding responses in proprietary documents without fine-tuning. * Internal Q\&A bots over company handbooks, policies, or codebases. * Adding domain-specific knowledge to a general model without prompt stuffing. * Reducing hallucinations by giving the model access to authoritative sources at query time. *** ## Prerequisites [Knowledge Bases](/docs/ai-studio/ai-engineering/knowledge-bases) are made to provide relevant and specific information for an LLM to use. To get started, see [Creating a Knowledge Base](/docs/ai-studio/ai-engineering/knowledge-bases), Knowledge Bases need to be enriched with the source documents and configured to expose chunks fitting the use case. The **name** of the Knowledge Base will be used as `knowledge_id` in the model generation. The `knowledge_bases` field is available on the Chat Completions endpoint (`/v3/router/chat/completions`) only, via the `orq` extension object. It is not supported on the Responses API (`/v3/router/responses`). ## Quick Start Using the created [Knowledge Base](/docs/ai-studio/ai-engineering/knowledge-bases), and its `id`, include the `knowledge_bases` payload within the model generation call. > The knowledge\_bases payload contains query configuration and search type, to learn more, see [Search Modes](/docs/ai-studio/ai-engineering/knowledge-bases#search-modes) and [Chunking Strategy](/docs/ai-studio/ai-engineering/knowledge-bases#datasource-and-chunking). ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [{"role": "user", "content": "How can I upgrade my account?"}], "orq": { "knowledge_bases": [ { "knowledge_id": "api-documentation", "top_k": 5, "threshold": 0.7, "search_type": "hybrid_search" } ] } }' ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await openai.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "How can I upgrade my account?" }], orq: { knowledge_bases: [ { knowledge_id: "api-documentation", top_k: 5, threshold: 0.7, search_type: "hybrid_search", }, ], }, }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.6-sol", messages=[{"role": "user", "content": "How can I upgrade my account?"}], extra_body={ "orq": { "knowledge_bases": [ { "knowledge_id": "api-documentation", "top_k": 5, "threshold": 0.7, "search_type": "hybrid_search", } ] } }, ) ``` **Orq** will automatically enrich the model generation with the given context and query to the knowledge base Knowledge retrieval step shown in the Traces view, where Orq.ai injects context from the linked Knowledge Base. [Knowledge Base](/docs/ai-studio/ai-engineering/knowledge-bases) # Load balancing across providers Source: https://docs.orq.ai/docs/ai-gateway/features/load-balancing Distribute LLM requests across providers using latency-based, weight-based, or round-robin routing to optimize costs, run A/B tests, and ensure redundancy. **Use Cases** * Distributing traffic across multiple provider accounts to stay within per-key rate limits. * A/B testing providers by routing a configurable percentage of traffic to each. * Spreading traffic across multiple providers to reduce blast radius from a single provider outage. * Maximizing throughput when one provider's capacity is a bottleneck. * Minimizing response time by routing to the fastest available model. *** ## Quick Start Distribute requests across multiple providers using weighted routing. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Write a marketing slogan", "load_balancer": { "type": "weight_based", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.7}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.3} ] } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Write a marketing slogan", load_balancer: { type: "weight_based", models: [ { model: "openai/gpt-5.4-mini", weight: 0.7 }, { model: "anthropic/claude-haiku-4-5-20251001", weight: 0.3 }, ], }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Write a marketing slogan", extra_body={ "load_balancer": { "type": "weight_based", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.7}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.3}, ], } }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Write a marketing slogan" }], load_balancer: { type: "weight_based", models: [ { model: "openai/gpt-5.4-mini", weight: 0.7 }, { model: "anthropic/claude-haiku-4-5-20251001", weight: 0.3 }, ], }, }); ``` ## Configuration | Parameter | Type | Required | Description | | ---------------------- | ------ | -------- | ----------------------------------------------------------------- | | `load_balancer` | Object | Yes | Load balancer configuration (top-level) | | `load_balancer.type` | string | Yes | Strategy type (`weight_based`, `round_robin`, or `latency_based`) | | `load_balancer.models` | Array | Yes | List of models with weights | | `models[].model` | string | Yes | Model identifier | | `models[].weight` | number | Yes | Weight assigned to this model (0.001 - 1.0) | **Weight Calculation:** * Weights are normalized: `[0.4, 0.8]` → `[33%, 67%]`. * Higher weight = more traffic. * Minimum weight: `0.001`. * Weight is ignored for round robin selection; every model in the list still receives an equal share of traffic. A matching [Routing Rule](/docs/ai-gateway/configuration/routing-rules#providers-and-traffic-weight) with its own target models configured overwrites this `load_balancer` parameter entirely, regardless of what the request sends. The Routing Rule's own strategy and models are used instead; the two configurations are never merged. `load_balancer` selects one model per request. It does not retry a failed call against another model in the pool. The top-level `model` is used as-is only when `load_balancer` cannot produce a selection, for example an empty `models` list. To fail over when a call errors, pair `load_balancer` with [Fallbacks](/docs/ai-gateway/features/retries#fallbacks). ## Latency-based routing Route each request to the model with the lowest recently observed latency, instead of a fixed traffic split. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Write a marketing slogan", "load_balancer": { "type": "latency_based", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.5}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.5} ] } }' ``` **How selection works:** * Latency is tracked per model as a running average that weights recent calls more heavily, so the selection adapts quickly when a provider speeds up or slows down. * With fresh data (samples from the last 5 minutes) for every configured model, the lowest-latency model is selected. A model with no data, or stale data, is probed again instead of being written off. * Ten percent of selections explore the full pool by configured weight to keep latency measurements fresh. Failed calls are penalized so a fast failure does not look like a fast success. * Configured weights decide exploration and near-ties: with the two fastest models within 0.5 ms of each other, the higher weight wins. With clear data, the lowest latency always wins. ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart TD A["New request"] --> B{"10% exploration draw?"} B -- "Yes" --> C["Pick a model by configured weight"] B -- "No" --> D{"Fresh data for every model? (< 5 min old)"} D -- "No" --> E["Probe an unknown or stale model by weight"] D -- "Yes" --> F{"Lowest two latencies tie within 0.5 ms?"} F -- "Yes" --> G["Higher configured weight wins"] F -- "No" --> H["Lowest latency wins"] C --> I["Call the model"] E --> I G --> I H --> I I --> J["Record duration, or a penalty on failure, into the latency history"] ``` To apply load balancing across your organization without changing request code, use **Routing Rules** to configure Fallback, Latency, Weighted, and Round Robin strategies at the workspace level. ## Weight-based routing Split traffic across models by percentage weights instead of latency or a fixed rotation. ### Patterns ```typescript Weight-based config patterns theme={"theme":{"light":"github-light","dark":"github-dark"}} // Equal distribution load_balancer: { type: "weight_based", models: [ { model: "openai/gpt-5.6-sol", weight: 1.0 }, { model: "anthropic/claude-sonnet-5", weight: 1.0 }, ], } // Cost optimization (cheap model primary) load_balancer: { type: "weight_based", models: [ { model: "openai/gpt-5.4-mini", weight: 0.8 }, { model: "openai/gpt-5.6-sol", weight: 0.2 }, ], } // A/B testing load_balancer: { type: "weight_based", models: [ { model: "current-model", weight: 0.9 }, { model: "experimental-model", weight: 0.1 }, ], } // Multi-provider redundancy load_balancer: { type: "weight_based", models: [ { model: "openai/gpt-5.6-sol", weight: 0.5 }, { model: "anthropic/claude-sonnet-5", weight: 0.3 }, { model: "azure/gpt-5.6-sol", weight: 0.2 }, ], } ``` ### Use cases | Scenario | Approach | Example | | ----------------------- | -------------------------- | --------------------------------- | | **Cost optimization** | Heavy on cheaper models | 80% GPT-5.4 Mini, 20% GPT-5.6 Sol | | **Performance testing** | Small traffic to new model | 95% current, 5% experimental | | **Provider redundancy** | Split across providers | 60% OpenAI, 40% Anthropic | | **Capacity management** | Distribute during peaks | Even split across models | ## Round Robin routing Rotate through the configured models evenly, one request at a time, instead of splitting traffic by weight or latency. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Write a marketing slogan", "load_balancer": { "type": "round_robin", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.5}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.5} ] } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Write a marketing slogan", load_balancer: { type: "round_robin", models: [ { model: "openai/gpt-5.4-mini", weight: 0.5 }, { model: "anthropic/claude-haiku-4-5-20251001", weight: 0.5 }, ], }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Write a marketing slogan", extra_body={ "load_balancer": { "type": "round_robin", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.5}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.5}, ], } }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Write a marketing slogan" }], load_balancer: { type: "round_robin", models: [ { model: "openai/gpt-5.4-mini", weight: 0.5 }, { model: "anthropic/claude-haiku-4-5-20251001", weight: 0.5 }, ], }, }); ``` **How selection works:** * Each request selects the next model in the list in turn, wrapping back to the first model after the last one. With two models, requests alternate; with three, the rotation cycles through all three. * Weights must be included in the request but do not affect selection: round robin is purely rotational, so every model receives an equal share of traffic over time regardless of the weights. * Round robin only selects a model per request; it does not retry or fail over a failed call. Round robin selects one model per request but does not fail over when that model errors. To add failover on top of round robin, pair `load_balancer` with [Fallbacks](/docs/ai-gateway/features/retries#fallbacks). ## Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Write a creative marketing slogan for an eco-friendly coffee brand", "load_balancer": { "type": "weight_based", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.4}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.6} ] } }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Write a creative marketing slogan for an eco-friendly coffee brand" } ], "load_balancer": { "type": "weight_based", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.4}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.6} ] } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Write a creative marketing slogan for an eco-friendly coffee brand", load_balancer: { type: "weight_based", models: [ { model: "openai/gpt-5.4-mini", weight: 0.4 }, { model: "anthropic/claude-haiku-4-5-20251001", weight: 0.6 }, ], }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Write a creative marketing slogan for an eco-friendly coffee brand", extra_body={ "load_balancer": { "type": "weight_based", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.4}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.6}, ], } }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [ { role: "user", content: "Write a creative marketing slogan for an eco-friendly coffee brand", }, ], load_balancer: { type: "weight_based", models: [ { model: "openai/gpt-5.4-mini", weight: 0.4 }, { model: "anthropic/claude-haiku-4-5-20251001", weight: 0.6 }, ], }, }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Write a creative marketing slogan for an eco-friendly coffee brand", } ], extra_body={ "load_balancer": { "type": "weight_based", "models": [ {"model": "openai/gpt-5.4-mini", "weight": 0.4}, {"model": "anthropic/claude-haiku-4-5-20251001", "weight": 0.6}, ], } }, ) ``` ## Monitoring Track these metrics for optimal load balancing: ```typescript Metrics tracking example theme={"theme":{"light":"github-light","dark":"github-dark"}} // Example monitoring setup const metrics = { requestsByModel: {}, // Count per model costsByModel: {}, // Cost per model latencyByModel: {}, // Response time per model errorsByModel: {}, // Error rate per model }; ``` **Key Metrics:** * **Traffic distribution**: Actual vs expected percentages. * **Cost per model**: Monitor spending across providers. * **Response times**: Compare latency by model. * **Error rates**: Track failures by provider. With `latency_based`, response time is already the selection signal. Manual weight tuning for performance is not needed; adjust weights only to influence exploration, cold-start probing, and near-tie behavior. ## Troubleshooting **Uneven distribution** * Check if weights are normalized correctly. * Verify sufficient request volume (min 100 requests for accuracy). * Monitor over longer time periods. **Unexpected costs** * Track actual vs expected cost distribution. * Monitor for expensive model overuse. * Set up cost alerts per provider. **Performance issues** * Check latency differences between models. * Monitor for provider-specific slowdowns. * Adjust weights based on performance data. **All traffic going to one model with `latency_based`** * Expected once one model is consistently fastest. Ten percent of requests still explore the rest of the pool to keep their latency data fresh. * Confirm `load_balancer.type` is set to the intended strategy if an even split was expected instead. **Selection is slow to adapt after a deploy or restart** * Expected. Latency history is in-memory: after a restart, every model is treated as unknown until fresh samples are collected. ## Limitations * **Probabilistic routing**: Short-term traffic may not match exact weights. * **Minimum volume needed**: Requires sufficient requests for statistical accuracy. * **Response variations**: Different models may return varying output quality. * **Cost complexity**: Managing billing across multiple providers. * **Provider dependencies**: Requires API access to all models. * **In-memory latency state**: With `latency_based`, latency history is in-memory and does not persist across restarts. ## Advanced weight-based usage **Environment-specific weights:** ```typescript Environment weight config theme={"theme":{"light":"github-light","dark":"github-dark"}} const weights = { development: { type: "weight_based", models: [ { model: "openai/gpt-5.4-mini", weight: 1.0 }, // Cheap for dev ], }, production: { type: "weight_based", models: [ { model: "openai/gpt-5.6-sol", weight: 0.7 }, // Quality primary { model: "anthropic/claude-sonnet-5", weight: 0.3 }, // Backup ], }, }; ``` **Dynamic weight adjustment:** ```typescript Dynamic weight calculation theme={"theme":{"light":"github-light","dark":"github-dark"}} // Adjust weights based on performance const calculateWeight = (latency: number, cost: number, quality: number) => quality / (latency * cost); const adjustWeights = (models) => ({ type: "weight_based", models: models.map((model) => ({ model: model.model, weight: calculateWeight(model.latency, model.cost, model.quality), })), }); ``` **With other features:** ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "model": "openai/gpt-5.6-sol", "load_balancer": { "type": "weight_based", "models": [ { "model": "openai/gpt-5.6-sol", "weight": 0.6 }, { "model": "anthropic/claude-sonnet-5", "weight": 0.4 } ] }, "retry": { "count": 2, "on_codes": [429] }, "timeout": { "call_timeout": 15000 } } ``` # Image, PDF, and audio: multimodal inputs and generation Source: https://docs.orq.ai/docs/ai-gateway/features/multimodal Send images, PDFs, and audio to LLMs, and generate images and speech through the AI Gateway. One unified OpenAI-compatible API for all modalities. **Use Cases** * Analyzing uploaded images or PDFs without a separate preprocessing pipeline. * Generating images from text prompts through the same API and key. * Transcribing audio recordings or voice inputs for downstream processing. * Extracting structured data from scanned documents, receipts, or screenshots. *** The **AI Gateway** supports all input and output modalities through a single OpenAI-compatible API, including the [Responses API](/docs/ai-gateway/features/responses-api). All endpoints share the same base URL, authentication, and **AI Gateway** features: [fallbacks](/docs/ai-gateway/features/retries#fallbacks), [caching](/docs/ai-gateway/features/cache), [load balancing](/docs/ai-gateway/features/load-balancing), and [retries](/docs/ai-gateway/features/retries). | Modality | Endpoint | | ------------------------------------- | --------------------------------------------------------------- | | [Image input](#image-input) | `POST /v3/router/responses`, `POST /v3/router/chat/completions` | | [PDF input](#pdf-input) | `POST /v3/router/responses`, `POST /v3/router/chat/completions` | | [Image generation](#image-generation) | `POST /v3/router/images/generations` | | [Image editing](#image-editing) | `POST /v3/router/images/edits` | | [Image variations](#image-variations) | `POST /v3/router/images/variations` | | [Text to speech](#text-to-speech) | `POST /v3/router/audio/speech` | | [Transcription](#transcription) | `POST /v3/router/audio/transcriptions` | | [Translation](#translation) | `POST /v3/router/audio/translations` | All endpoints use the same base URL and authentication: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} BASE_URL=https://my.orq.ai/v3/router Authorization: Bearer $ORQ_API_KEY ``` This page covers sending and generating each modality end to end. Which models accept image, PDF, or audio **input**, the content part contract, and URL versus base64 transport are owned by [Sending files to models](/docs/ai-gateway/features/files). For the catalog by endpoint, see [Supported Models](/docs/ai-gateway/supported-models) or the [Providers](/docs/ai-gateway/providers-overview) page. Analyze images alongside text. Pass image URLs or base64-encoded files in `chat/completions` messages. Send PDF documents for extraction and analysis. Supported natively by compatible models. Generate, edit, and vary images using DALL-E 2, DALL-E 3, and GPT Image 1. Convert text to speech, transcribe audio files, and translate audio to English. ## Image input Analyze images alongside text using `POST /v3/router/responses` or `POST /v3/router/chat/completions`. Pass images as public URLs or base64-encoded data in the message content array. For the file lifecycle, see the [Files API](/docs/ai-studio/ai-engineering/files). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": [ { "role": "user", "content": [ {"type": "input_text", "text": "What is in this image? Describe in detail."}, {"type": "input_image", "image_url": "https://picsum.photos/seed/sample-photo/800/600"} ] } ] }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "What is in this image? Describe in detail."}, {"type": "image_url", "image_url": {"url": "https://picsum.photos/seed/sample-photo/800/600"}} ] } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const base64Image = fs.readFileSync("chart.png").toString("base64"); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: [ { role: "user", content: [ { type: "input_text", text: "Analyze this chart and extract the key data points" }, { type: "input_image", image_url: `data:image/png;base64,${base64Image}` }, ], }, ], }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import base64 import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") base64_image = encode_image("chart.png") response = client.responses.create( model="openai/gpt-5.6-sol", input=[ { "role": "user", "content": [ {"type": "input_text", "text": "Analyze this chart and extract the key data points"}, {"type": "input_image", "image_url": f"data:image/png;base64,{base64_image}"}, ], } ], ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const base64Image = fs.readFileSync("chart.png").toString("base64"); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: [ { type: "text", text: "Analyze this chart and extract the key data points" }, { type: "image_url", image_url: { url: `data:image/png;base64,${base64Image}` } }, ], }, ], }); console.log(response.choices[0].message.content); ``` ### Supported formats | Format | Use case | Max size | | ------------ | ---------------------- | ------------------- | | **JPEG/JPG** | Photos, general images | 20MB | | **PNG** | Screenshots, diagrams | 20MB | | **GIF** | Static images only | 20MB | | **WebP** | Modern web images | 20MB | | **Base64** | Embedded image data | Model context limit | | **URLs** | Public image links | Model context limit | ### Detail levels | Level | Resolution | Speed | Cost | Use case | | -------- | --------------- | ------ | ------ | ------------------ | | `"low"` | 512x512 | Fast | Low | Quick overview | | `"high"` | Full resolution | Slow | High | Detailed analysis | | `"auto"` | Model decides | Medium | Medium | Balanced (default) | Set `detail` in the `image_url` object: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "image_url", "image_url": { "url": "https://picsum.photos/seed/sample-photo/800/600", "detail": "high" } } ``` ### Patterns **Multiple images:** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: [ { role: "user", content: [ { type: "input_text", text: "Compare these before and after photos. What changes do you notice?" }, { type: "input_image", image_url: "https://picsum.photos/seed/before-photo/800/600" }, { type: "input_image", image_url: "https://picsum.photos/seed/after-photo/800/600" }, ], }, ], }); console.log(response.output_text); ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const content = [ { type: "text", text: "Compare these before and after photos. What changes do you notice?" }, { type: "image_url", image_url: { url: "https://picsum.photos/seed/before-photo/800/600", detail: "high" } }, { type: "image_url", image_url: { url: "https://picsum.photos/seed/after-photo/800/600", detail: "high" } }, ]; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content }], }); console.log(response.choices[0].message.content); ``` **OCR and text extraction:** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const imageUrl = "https://picsum.photos/seed/sample-photo/800/600"; const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: [ { role: "user", content: [ { type: "input_text", text: "Extract all text from this image. Return as plain text, preserving formatting where possible." }, { type: "input_image", image_url: imageUrl }, ], }, ], }); console.log(response.output_text); ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const imageUrl = "https://picsum.photos/seed/sample-photo/800/600"; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: [ { type: "text", text: "Extract all text from this image. Return as plain text, preserving formatting where possible.", }, { type: "image_url", image_url: { url: imageUrl, detail: "high" }, }, ], }, ], }); console.log(response.choices[0].message.content); ``` **Structured output:** ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os from pydantic import BaseModel from typing import List client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class ImageAnalysis(BaseModel): objects: List[str] text_content: str dominant_colors: List[str] confidence: float image_url = "https://picsum.photos/seed/sample-photo/800/600" response = client.chat.completions.parse( model="openai/gpt-5.6-sol", messages=[{ "role": "user", "content": [ {"type": "text", "text": "Analyze this image systematically"}, {"type": "image_url", "image_url": {"url": image_url}} ] }], response_format=ImageAnalysis ) ``` ### Limitations | Limitation | Details | Workaround | | --------------- | ----------------------- | ------------------------------- | | **File size** | 20MB max per image | Compress before upload | | **Image count** | Varies by model (5-16) | Process in batches | | **Video** | Static images only | Extract frames for analysis | | **Privacy** | Images sent to provider | Use on-premise models if needed | ## PDF input Send PDF documents directly in messages for analysis and content extraction using `POST /v3/router/responses` or `POST /v3/router/chat/completions`. PDF input support varies by model. See the [Supported Models](/docs/ai-gateway/supported-models) page and check your provider's documentation for PDF capability. For the file lifecycle and for turning uploaded documents into knowledge base datasources, see the [Files API](/docs/ai-studio/ai-engineering/files). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": [ { "role": "user", "content": [ {"type": "input_text", "text": "Please analyze this PDF document and provide a summary"}, {"type": "input_file", "filename": "document.pdf", "file_data": "data:application/pdf;base64,YOUR_BASE64_ENCODED_PDF"} ] } ] }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "Please analyze this PDF document and provide a summary"}, {"type": "file", "file": {"file_data": "data:application/pdf;base64,YOUR_BASE64_ENCODED_PDF", "filename": "document.pdf"}} ] } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const pdfBase64 = fs.readFileSync("document.pdf").toString("base64"); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: [ { role: "user", content: [ { type: "input_text", text: "Please analyze this PDF document and provide a summary" }, { type: "input_file", filename: "document.pdf", file_data: `data:application/pdf;base64,${pdfBase64}`, }, ], }, ], }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os import base64 client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) with open("document.pdf", "rb") as pdf_file: pdf_base64 = base64.b64encode(pdf_file.read()).decode("utf-8") response = client.responses.create( model="openai/gpt-5.6-sol", input=[ { "role": "user", "content": [ {"type": "input_text", "text": "Please analyze this PDF document and provide a summary"}, { "type": "input_file", "filename": "document.pdf", "file_data": f"data:application/pdf;base64,{pdf_base64}", }, ], } ], ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const pdfBase64 = fs.readFileSync("document.pdf").toString("base64"); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: [ { type: "text", text: "Please analyze this PDF document and provide a summary" }, { type: "file", file: { file_data: `data:application/pdf;base64,${pdfBase64}`, filename: "document.pdf" } }, ], }, ], }); ``` The `input_file` and `file` content part fields are specified once, in [Sending files to models](/docs/ai-gateway/features/files#sending-an-image-vs-sending-a-document). ### Use cases | Scenario | Example prompt | | ---------------------- | ------------------------------------- | | **Contract analysis** | "Extract key terms and obligations" | | **Invoice processing** | "Extract amounts, dates, vendor info" | | **Research papers** | "Summarize methodology and findings" | | **Form extraction** | "Convert form data to JSON" | ### Limitations | Limitation | Details | Workaround | | --------------------- | -------------------------------------- | ---------------------- | | **File size** | Model context limits | Split large PDFs | | **Scanned documents** | Quality varies by model | Use OCR preprocessing | | **Complex layouts** | Tables and charts may not extract well | Use structured prompts | | **Security** | Sensitive documents sent to provider | Use on-premise models | ## Image generation Generate images from a text prompt using `POST /v3/router/images/generations`. For the full and up-to-date list of supported image models, see [Image models](/docs/ai-gateway/supported-models#image-models) on the Supported Models page. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.images.generate({ model: "openai/gpt-image-2", prompt: "A futuristic city skyline at sunset, photorealistic", n: 1, size: "1024x1024", }); console.log(response.data[0].b64_json?.slice(0, 40)); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) response = client.images.generate( model="openai/gpt-image-2", prompt="A futuristic city skyline at sunset, photorealistic", n=1, size="1024x1024", ) print(response.data[0].b64_json[:40]) ``` ### Parameters | Parameter | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------- | | `model` | Model ID | | `prompt` | Text description of the desired image | | `n` | Number of images to generate | | `size` | Image dimensions (see [Supported Models](/docs/ai-gateway/supported-models#image-models) for per-model sizes) | | `response_format` | `url` or `b64_json`. DALL-E 2/3 only; `gpt-image-1` always returns `b64_json` | | `quality` | Image quality level. Values vary by model | | `style` | `vivid` or `natural`. DALL-E 3 only | | `background` | `transparent`, `opaque`, or `auto`. `gpt-image-1` only | | `output_format` | `png`, `jpeg`, or `webp`. `gpt-image-1` only | | `output_compression` | Compression level 0-100%. `gpt-image-1` only | | `moderation` | `auto` or `low`. `gpt-image-1` only | | `stream` | Stream partial images as they're generated. OpenAI models only; see streaming constraints below | Set `response_format` to `url` to receive a hosted image link, or `b64_json` to receive the image inline as a base64-encoded string. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "created": 1234567890, "data": [ { "b64_json": "iVBORw0KGgo..." } ] } ``` Streaming image generation (`stream: true`) has these constraints: * `n` must be `1` * `response_format` is not supported * `size` must be `1024x1024`, `1024x1536`, `1536x1024`, or `auto` Requests outside these constraints return a 400 error before the stream opens. ### Image editing Modify an existing image using a prompt and an optional mask with `POST /v3/router/images/edits`. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import fs from "fs"; import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.images.edit({ model: "openai/gpt-image-2", image: fs.createReadStream("original.png"), prompt: "Add a sunset sky behind the buildings", size: "1024x1024", }); console.log(response.data[0].b64_json?.slice(0, 40)); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) with open("original.png", "rb") as image_file: response = client.images.edit( model="openai/gpt-image-2", image=image_file, prompt="Add a sunset sky behind the buildings", size="1024x1024", ) print(response.data[0].b64_json[:40]) ``` | Parameter | Description | | ----------------- | ---------------------------------------------------------------------- | | `model` | Model ID | | `image` | PNG, WEBP, or JPEG file to edit. Some models accept an array of images | | `prompt` | Text description of the desired edit | | `mask` | Optional PNG mask where transparent areas indicate where to edit | | `size` | Output image dimensions | | `response_format` | `url` or `b64_json`. `gpt-image-1` always returns `b64_json` | | `quality` | Image quality level. Values vary by model | ### Image variations Generate variations of an existing image with `POST /v3/router/images/variations`. See [Image models](/docs/ai-gateway/supported-models#image-models) for which models support variations. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import fs from "fs"; import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.images.createVariation({ model: "openai/dall-e-2", image: fs.createReadStream("original.png"), size: "1024x1024", response_format: "url", }); response.data.forEach((img) => console.log(img.url)); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) with open("original.png", "rb") as image_file: response = client.images.create_variation( model="openai/dall-e-2", image=image_file, size="1024x1024", response_format="url", ) for img in response.data: print(img.url) ``` | Parameter | Description | | ----------------- | --------------------------------------- | | `model` | Model ID | | `image` | PNG image to create a variation of | | `n` | Number of variations to generate (1-10) | | `size` | Output image dimensions | | `response_format` | `url` or `b64_json` | ### Fallbacks and reliability Image endpoints support the same `fallbacks` and `retry` parameters as chat completions: ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.images.generate({ model: "openai/gpt-image-2", prompt: "A mountain lake at dawn", size: "1024x1024", // @ts-ignore - orq.ai extension fallbacks: [{ model: "openai/dall-e-3" }, { model: "openai/dall-e-2" }], }); ``` ## Audio The **AI Gateway** exposes three OpenAI-compatible audio endpoints. All support [fallbacks](/docs/ai-gateway/features/retries#fallbacks), [load balancing](/docs/ai-gateway/features/load-balancing), and [retries](/docs/ai-gateway/features/retries). ### Text to speech Convert text to audio using `POST /v3/router/audio/speech`. | Provider | Model | | ---------- | ---------------------------------------- | | OpenAI | `openai/tts-1` | | OpenAI | `openai/tts-1-hd` | | OpenAI | `openai/gpt-4o-mini-tts` | | ElevenLabs | `elevenlabs/eleven_multilingual_v2` | | ElevenLabs | `elevenlabs/eleven_turbo_v2_5` | | ElevenLabs | `elevenlabs/eleven_flash_v2_5` | | ElevenLabs | `elevenlabs/eleven_flash_v2` | | Google AI | `google-ai/gemini-2.5-flash-preview-tts` | | Google AI | `google-ai/gemini-2.5-pro-preview-tts` | | Vertex AI | `google/gemini-2.5-flash-preview-tts` | | Vertex AI | `google/gemini-2.5-pro-preview-tts` | For the full and up-to-date list of TTS models, see [Text-to-Speech models](/docs/ai-gateway/supported-models#text-to-speech-models) on the Supported Models page. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.audio.speech.create({ model: "openai/tts-1", voice: "alloy", input: "Hello, welcome to Acme Corp. How can I help you today?", response_format: "mp3", }); const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync("output.mp3", buffer); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) with client.audio.speech.with_streaming_response.create( model="openai/tts-1", voice="alloy", input="Hello, welcome to Acme Corp. How can I help you today?", response_format="mp3", ) as response: response.stream_to_file("output.mp3") ``` **Streaming:** Process audio chunks in real time as they arrive, useful for low-latency playback pipelines. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const processAudioChunk = (chunk: Uint8Array) => {}; const response = await client.audio.speech.create({ model: "openai/tts-1", voice: "alloy", input: "Hello, welcome to Acme Corp. How can I help you today?", response_format: "pcm", }); const reader = response.body!.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; processAudioChunk(value); } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) def process_audio_chunk(chunk: bytes) -> None: pass with client.audio.speech.with_streaming_response.create( model="openai/tts-1", voice="alloy", input="Hello, welcome to Acme Corp. How can I help you today?", response_format="pcm", ) as response: for chunk in response.iter_bytes(chunk_size=1024): process_audio_chunk(chunk) ``` **Parameters:** | Parameter | Description | | ----------------- | -------------------------------------------------------------------------------------------- | | `model` | Model ID | | `input` | Text to synthesize. Maximum length varies by provider | | `voice` | Voice ID. See voices table below | | `response_format` | Output format: `mp3`, `opus`, `aac`, `flac`, `wav`, `pcm`. Supported values vary by provider | | `speed` | Playback speed of the generated audio | **Voices:** | Provider | Voices | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | OpenAI | `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer` | | ElevenLabs | `aria`, `roger`, `sarah`, `laura`, `charlie`, `george`, `callum`, `river`, `liam`, `charlotte`, `alice`, `matilda`, `will`, `jessica`, `eric`, `chris` | ### Transcription Transcribe an audio file to text using `POST /v3/router/audio/transcriptions`. | Provider | Model | | ---------- | ------------------------------- | | OpenAI | `openai/whisper-1` | | OpenAI | `openai/gpt-4o-transcribe` | | OpenAI | `openai/gpt-4o-mini-transcribe` | | ElevenLabs | `elevenlabs/scribe_v1` | | Groq | `groq/whisper-large-v3` | | Groq | `groq/whisper-large-v3-turbo` | | Mistral | `mistral/voxtral-mini-2507` | | Azure | `azure/whisper` | For the full and up-to-date list of transcription models, see [Speech-to-Text models](/docs/ai-gateway/supported-models#speech-to-text-models) on the Supported Models page. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const transcription = await client.audio.transcriptions.create({ model: "openai/gpt-4o-transcribe", file: fs.createReadStream("meeting.mp3"), response_format: "json", language: "en", }); console.log(transcription.text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) with open("meeting.mp3", "rb") as audio_file: transcription = client.audio.transcriptions.create( model="openai/gpt-4o-transcribe", file=audio_file, response_format="json", language="en", ) print(transcription.text) ``` **Parameters:** | Parameter | Description | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `model` | Model ID | | `file` | Audio file to transcribe. Supported formats: `flac`, `mp3`, `mp4`, `mpeg`, `mpga`, `m4a`, `ogg`, `wav`, `webm` | | `language` | ISO-639-1 language code of the input audio (e.g. `en`, `fr`, `de`) | | `prompt` | Optional text to guide the model's style or continue a previous segment | | `response_format` | `json`, `text`, `srt`, `verbose_json`, or `vtt` | | `temperature` | Sampling temperature between 0 and 1 | | `timestamp_granularities` | Array of granularities: `["word"]`, `["segment"]`, or `["word", "segment"]`. Requires `verbose_json`. Not supported by all models | | `diarize` | Annotate which speaker is talking in the file. ElevenLabs only | | `num_speakers` | Maximum number of speakers to identify. ElevenLabs only | | `tag_audio_events` | Tag non-speech events such as `(laughter)` or `(applause)`. ElevenLabs only | | `enable_logging` | Set to `false` to disable logging and enable zero data retention | ### Translation The OpenAI translation endpoint only supports `openai/whisper-1`. `gpt-4o-transcribe` and `gpt-4o-mini-transcribe` do not support translation. Transcribe and translate audio to English using `POST /v3/router/audio/translations`. The output is always in English regardless of the source language. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const translation = await client.audio.translations.create({ model: "openai/whisper-1", file: fs.createReadStream("interview_french.mp3"), response_format: "json", }); console.log(translation.text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) with open("interview_french.mp3", "rb") as audio_file: translation = client.audio.translations.create( model="openai/whisper-1", file=audio_file, response_format="json", ) print(translation.text) ``` Translation supports the same `response_format` and `temperature` parameters as transcription. # OCR via AI Gateway Source: https://docs.orq.ai/docs/ai-gateway/features/ocr Extract text and structure from documents and images through the AI Gateway with a dedicated OCR endpoint. **Use Cases** * Extracting text and layout from scanned PDFs and images. * Digitizing receipts, invoices, and forms into structured markdown. * Preprocessing documents for downstream RAG or analytics pipelines. * Turning uploaded files into searchable, machine-readable content. *** ## Overview Use **`POST /v3/router/ocr`** on the **AI Gateway** to run OCR on a document or image. The endpoint returns per-page markdown that preserves document structure and hierarchy, along with any extracted images. For the full request and response schema, see the [Create OCR](/reference/ocr/create-ocr) API reference. ## Quick start Send a document URL or image URL and specify the OCR model to use. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "https://my.orq.ai/v3/router/ocr" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "mistral/mistral-ocr-4-0", "document": { "type": "document_url", "document_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", "document_name": "dummy.pdf" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_API_KEY, }); const result = await orq.router.ocr({ model: "mistral/mistral-ocr-4-0", document: { type: "document_url", documentUrl: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", documentName: "dummy.pdf", }, }); console.log(result); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os with Orq(api_key=os.getenv("ORQ_API_KEY", "")) as orq: res = orq.router.ocr( model="mistral/mistral-ocr-4-0", document={ "type": "document_url", "document_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", "document_name": "dummy.pdf", }, ) print(res) ``` ## Input types The `document` field accepts either a document URL or an image URL. | Type | Field | Description | | -------------- | -------------- | ------------------------------------------------------------------------------------ | | `document_url` | `document_url` | URL of the document (e.g. PDF) to process. Optional `document_name`. | | `image_url` | `image_url` | Base64-encoded image data, or an object with a `url` and an optional `detail` field. | ## Options Additional fields control which pages are processed and how images are returned. | Field | Description | | ------------------------------------ | ---------------------------------------------------------------------------- | | `pages` | Array of 0-based page indices to process. Omit or pass `null` for all pages. | | `ocr_settings.include_image_base64` | Return extracted images inline as base64 in the response. | | `ocr_settings.max_images_to_include` | Maximum number of images to include per page. | | `ocr_settings.image_min_size` | Minimum height and width (pixels) for an image to be included. | ## Response Each response includes the model used, an array of pages with extracted `markdown` and `images`, and a `usage` object counting pages or tokens processed. See the [Create OCR](/reference/ocr/create-ocr) reference for the full schema. ## Supported models For the current list of OCR models, see [OCR models](/docs/ai-gateway/supported-models#ocr-models) on the Supported Models page. # OpenAI-compatible API Source: https://docs.orq.ai/docs/ai-gateway/features/openai-compatible-api Use Orq.ai as an OpenAI-compatible API proxy. Access 500+ LLM models with the existing OpenAI SDK by changing only the base URL. The **AI Gateway** exposes endpoints that are fully compatible with the OpenAI API, letting you use every model available in the **AI Gateway** without changing your application logic. Keep the existing OpenAI client, point it to the Orq.ai proxy baseURL, and continue as usual. For the OpenAI API specification, see [the API Reference](https://platform.openai.com/docs/api-reference/chat) To move an existing application from the OpenAI SDK, **OpenRouter**, or **LiteLLM**, see [Migrate to Orq.ai](/docs/ai-gateway/get-started/migrate). **Drop-in Integration (No Code Changes)** 1. Keep your existing OpenAI SDK or HTTP integration. 2. Set the base URL to `https://my.orq.ai/v3/router` 3. Use the Orq.ai [API Key](/docs/ai-studio/organization/api-keys) in the Authorization header. 4. Call the same endpoints and payloads already used with OpenAI. ## Base URL OpenAI-compatible endpoint: ```text URL theme={"theme":{"light":"github-light","dark":"github-dark"}} https://my.orq.ai/v3/router ``` All routes below are relative to this base URL and mirror OpenAI’s request/response formats. ## Authentication Authenticate with the Orq.ai API key via the `Authorization: Bearer $ORQ_API_KEY` header. To learn more about Orq API Key, see [API Key](/docs/ai-studio/organization/api-keys). Minimum headers: * `Authorization: Bearer $ORQ_API_KEY` * `Content-Type: application/json` ## Supported Endpoints Schema, parameters, and response formats match the OpenAI API. * `GET /models`: List available models * `GET /models/{model}`: Get model details * `POST /chat/completions`: Chat completions (supports streaming, images, files, and tool calls) * `POST /completions`: Text completions * `POST /embeddings`: Vector embeddings ([Embeddings](/docs/ai-gateway/features/embeddings)) * `POST /images/generations`: Image generation * `POST /images/edits`: Image editing * `POST /images/variations`: Image variations * `POST /moderations`: Text moderation ([Rerank and Moderations](/docs/ai-gateway/features/rerank-and-moderations#moderations)) * `POST /rerank`: Rerank results ([Rerank and Moderations](/docs/ai-gateway/features/rerank-and-moderations#rerank)) * `POST /speech`: Text-to-speech * `POST /audio/transcriptions`: Transcribe audio into the input language * `POST /audio/translations`: Translate audio into the input language * `POST /responses`: Create a model response with built-in tools ([Responses API](/docs/ai-gateway/features/responses-api), [Web Search](/docs/ai-gateway/features/web-search)) ## Models Use the model field exactly as you would with OpenAI, substituting the ID of any model available in the [AI Gateway Models](/docs/ai-gateway/using-the-router). ## Error Handling & Compatibility Notes * HTTP status codes and error structures follow OpenAI’s conventions. * Streaming, function/tool calls, and multimodal inputs (images/files) are supported on /chat/completions. * The AI Gateway is versioned under `/v3/router`. Ensure clients target this path. *** # Plugins Source: https://docs.orq.ai/docs/ai-gateway/features/plugins/overview Plugins are request-scoped transforms that mutate the text exchanged with a model in the AI Gateway, such as PII redaction. **Plugins** are request-scoped transforms applied to the text exchanged with a model in the **AI Gateway**. A plugin mutates the request before the provider sees it and can restore the response on the way back. This sets plugins apart from [Guardrails](/docs/ai-gateway/configuration/guardrails), which only observe and block. Attach plugins per request through the `plugins` array. Each plugin is identified by an `id` discriminator. Replace personally identifiable information with placeholders before the provider sees it, then restore the original values in the response. Repair malformed JSON in model output, such as markdown code fences, trailing commas, and missing brackets. ## Supported endpoints The `plugins` array is accepted on the following **AI Gateway** endpoints: * [`POST /v3/router/responses`](/reference/responses/create-response) * [`POST /v3/router/chat/completions`](/reference/chat/create-chat-completion) * [`POST /v3/router/completions`](/reference/completions/create-completion) * [`POST /v3/router/embeddings`](/reference/embeddings/create-embeddings): input only, no restore * [`POST /v3/router/rerank`](/reference/rerank/create-rerank): input only, echoed text restored * [`POST /v3/router/images/generations`](/reference/images/create-image): input only, no restore # PII Redaction Source: https://docs.orq.ai/docs/ai-gateway/features/plugins/pii-redaction Redact personally identifiable information before it reaches the provider, then restore values. The `pii_redaction` **plugin** modifies request and response content directly. It runs on every matching request without needing a separate rule condition. ## Use cases * Keeping names, emails, and account numbers out of third-party provider logs. * Meeting data-handling requirements without rewriting prompts in every service. * Sending sensitive support tickets to a model while preserving the reply for the end user. * Excluding raw PII from trace storage while still tracing the request end to end. The `pii_redaction` plugin detects personally identifiable information in the request, replaces each value with a placeholder before the provider sees it, and restores the original values in the response. The provider receives only placeholders such as `` or ``. This feature is in Beta. ## Quick start Add a `pii_redaction` entry to the `plugins` array. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Email the invoice to jane.doe@example.com", "plugins": [{ "id": "pii_redaction", "language": "en" }] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.responses.create({ model: 'openai/gpt-5.4-mini', input: 'Email the invoice to jane.doe@example.com', // @ts-ignore - orq.ai extension plugins: [{ id: 'pii_redaction', language: 'en' }], }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.responses.create( model="openai/gpt-5.4-mini", input="Email the invoice to jane.doe@example.com", extra_body={ "plugins": [{"id": "pii_redaction", "language": "en"}] }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Email the invoice to jane.doe@example.com' }, ], // @ts-ignore - orq.ai extension plugins: [{ id: 'pii_redaction', language: 'en' }], }); ``` ## Workspace-level redaction Enable PII redaction for every request from **Settings** > **Plugins**, without passing a `plugins` array on each call. Once enabled, it applies automatically to every call that doesn't already specify a `pii_redaction` plugin. Plugins settings page with a PII Redaction card showing an enable toggle, and PDF Inputs and Response Healing cards labeled Coming soon. Once the toggle is on, a icon appears next to it. Click it to open the [Configuration](#configuration) panel. A request cannot turn off or reduce the workspace-level redaction settings. It can only add stricter rules of its own, such as lowering the detection threshold to redact more. ## Apply per routing rule Attach a `pii_redaction` entry to a [Routing Rule](/docs/ai-gateway/configuration/routing-rules) to run redaction on the traffic that rule matches, with its own per-rule configuration. ## How it works The plugin runs a redaction round-trip around the generation: 1. **Redact**: detected PII in the request is replaced with typed placeholders before the request leaves the **AI Gateway**. 2. **Generate**: the provider processes the placeholder text and returns a response that keeps the placeholders intact. 3. **Restore**: the original values are substituted back into the response before it returns to the caller. On `embeddings`, `rerank`, and `images/generations`, the plugin redacts the input only. The `rerank` response restores the echoed document text; `embeddings` and image generation have no echoed text to restore. Detection runs on **Orq.ai**'s own model, hosted on **Orq.ai** infrastructure. Text is never sent to a third-party service for PII detection. ## Configuration | Parameter | Type | Required | Description | | ---------------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Yes | Plugin discriminator. Must be `pii_redaction`. | | `language` | string | No | Detector language: `en`, `nl`, or `auto` to detect it per request. Defaults to `en`. Read the live set from `GET /v2/pii/capabilities`. | | `regions` | string\[] | No | Region coverage. Lowercase ISO 3166-1 alpha-2 codes, or `["all"]`. Redacts every entity type the listed regions gate, alongside the base catalog. | | `entities` | string\[] | No | Explicit coverage. UPPERCASE entity types to redact. Alone it is a strict allowlist; alongside `regions` it adds to the region coverage. Omit both to redact every region. | | `entity_thresholds` | object | No | Per-entity confidence cutoffs in `[0,1]`, keyed by entity type. Every key must also appear in `entities`. | | `on_failure` | string | No | Behavior when redaction is unavailable: `block` or `passthrough`. Defaults to `block`. | | `threshold` | number | No | Global detector confidence cutoff in `[0,1]`, applied to every type. Defaults to `0.5`. | | `persist_redacted_to_traces` | boolean | No | Store the redacted form in traces rather than the restored original. Defaults to `true`. | `GET /v2/pii/capabilities` is the source of truth for the live entity catalog, the supported regions, the region-to-entity mapping (`region_entities`), the supported languages, and the default thresholds. The catalog grows with the service, so treat any list on this page as a snapshot rather than the full set. ### Detection threshold The `threshold` parameter sets the global first-pass confidence score at which a detected value is counted as PII, and it applies to every type. The range is `0` to `1`; the default is `0.5`. | Value | Behavior | | ----- | --------------------------------------------------------------------- | | `1` | Very high confidence required: fewer detections, more missed values. | | `0` | Almost no confidence required: more detections, more false positives. | Scores are boosted for language-specific formats (for example, a Dutch BSN scores higher when the detected or selected language is Dutch). Adjust the threshold only when there is an observed problem: lower it if real PII is being missed, raise it if too many non-PII values are being redacted. When `entity_thresholds` names a type, that value replaces `threshold` for the type and may sit above or below it. A higher value detects less of that type, a lower value detects more, and types without an override stay at `threshold`. ### Coverage selection Coverage is selected two ways. `regions` covers whole regions: every entity type the listed regions gate is redacted, alongside the base catalog. `entities` is an explicit list of entity types to redact, and on its own it is strict — nothing outside the list is redacted. Set both and the two are unioned: the region coverage plus the named types, so `entities` widens rather than narrows. Omit both and every region is redacted: selecting nothing is the widest request, not the narrowest. That default is gated on `entities` being empty, so it can never loosen an allowlist — `entities` alone stays strict. #### Regions Region codes are lowercase ISO 3166-1 alpha-2, for example `nl`, `gb`, `be`, `de`, `fr`, and `us`. The United Kingdom is `gb`, never `uk`. The value `["all"]` is exclusive: it covers every supported region and cannot be combined with other region codes. Read the live set of supported regions and the types each one gates from `GET /v2/pii/capabilities`. ```json Regions theme={"theme":{"light":"github-light","dark":"github-dark"}} { "id": "pii_redaction", "language": "en", "regions": ["nl", "gb"], "threshold": 0.6, "on_failure": "block" } ``` #### Entity types Entity type names are UPPERCASE, for example `PERSON`, `EMAIL_ADDRESS`, and `BSN`. A named region-specific type fires even without naming its region, so the types belonging to a region can be picked individually instead of taking the whole region. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Contact Jane Doe at jane.doe@example.com", "plugins": [{ "id": "pii_redaction", "language": "en", "entities": ["EMAIL_ADDRESS"] }] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.responses.create({ model: 'openai/gpt-5.4-mini', input: 'Contact Jane Doe at jane.doe@example.com', // @ts-ignore - orq.ai extension plugins: [ { id: 'pii_redaction', language: 'en', entities: ['EMAIL_ADDRESS'] }, ], }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.responses.create( model="openai/gpt-5.4-mini", input="Contact Jane Doe at jane.doe@example.com", extra_body={ "plugins": [{"id": "pii_redaction", "language": "en", "entities": ["EMAIL_ADDRESS"]}] }, ) ``` #### Per-entity thresholds `entity_thresholds` maps an entity type to a confidence cutoff in `[0,1]`. It only tunes confidence; it never changes which types are redacted. Every key must also appear in `entities`: a key that is absent from `entities`, or any key at all while `entities` is empty, is rejected with a validation error. Per-entity tuning applies to the types named in `entities`. To tune a type a region gates, name that type in `entities` — alongside `regions` if the rest of the region should stay covered — and set its cutoff. `region_entities` from `GET /v2/pii/capabilities` lists the types each region gates. ```json Entity types with per-entity thresholds theme={"theme":{"light":"github-light","dark":"github-dark"}} { "id": "pii_redaction", "language": "en", "entities": ["PERSON", "EMAIL_ADDRESS", "BSN"], "entity_thresholds": { "PERSON": 0.85, "BSN": 0.7 }, "threshold": 0.5, "on_failure": "block" } ``` ## Failure modes The `on_failure` policy decides what happens when the detection service cannot run: | Mode | Behavior | | ------------- | -------------------------------------------------------------------------------- | | `block` | Fails closed. The request is rejected and nothing reaches the provider. Default. | | `passthrough` | Fails open. The original, un-redacted text is sent to the provider. | The detection service waits up to 90 seconds by default before timing out and applying the `on_failure` policy. ### The guardrail is always fail-closed `on_failure` belongs to the `pii_redaction` plugin only. The `orq_pii_detection` guardrail always fails closed: if the detection service is unavailable or the detect call errors, the text is treated as containing PII and the request is blocked. There is no way to make a guardrail fail open, and setting `on_failure` in a guardrail's `options` is rejected at write time rather than silently ignored: a safety control whose stored configuration disagrees with what it does is worse than one with no setting at all. Use the plugin when you need configurable failure behavior. One consequence is worth knowing before an incident rather than during one: a guardrail rule with no project and no expression applies workspace-wide, because project-less rules are indexed as globals and an empty expression matches every request. A detection service outage therefore blocks all `/responses`, `/chat/completions` and `/messages` traffic in that workspace. That is the intended fail-closed behavior, not a defect, but it means a workspace-wide PII guardrail couples inference availability to the availability of the detection service. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Email the invoice to jane.doe@example.com", "plugins": [{ "id": "pii_redaction", "language": "en", "on_failure": "passthrough" }] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.responses.create({ model: 'openai/gpt-5.4-mini', input: 'Email the invoice to jane.doe@example.com', // @ts-ignore - orq.ai extension plugins: [{ id: 'pii_redaction', language: 'en', on_failure: 'passthrough' }], }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.responses.create( model="openai/gpt-5.4-mini", input="Email the invoice to jane.doe@example.com", extra_body={ "plugins": [{"id": "pii_redaction", "language": "en", "on_failure": "passthrough"}] }, ) ``` ## Supported entity types `GET /v2/pii/capabilities` is the live catalog and the only authoritative list: it grows with the detector, and `languages`, `base_entities`, `all_entities`, `regions` and `region_entities` all come from it. The lists below are a snapshot for orientation. Base entity types need no region: they are detected whenever `regions` is set. Setting neither `regions` nor `entities` covers every region, base types included. Regional types are detected when their region is selected, or when the type is named in `entities`. With `entities` alone nothing is detected but the named types, base types included. The base list is what the catalog classifies as region-independent, not what a given detector build emits in practice. Several of these types have narrower recognizers than the catalog entry suggests, so probe the detector before relying on a specific type firing with no configuration. `AGE`, `API_KEY`, `BANK_ROUTING`, `BIC`, `BIOMETRIC_ID`, `BLOOD_TYPE`, `CREDIT_CARD`, `CRYPTO`, `DATE_TIME`, `DEVICE_ID`, `EDUCATION_LEVEL`, `EMAIL_ADDRESS`, `EMPLOYMENT_STATUS`, `GENDER`, `HEALTH_PLAN_ID`, `HTTP_COOKIE`, `IBAN_CODE`, `ID`, `ID_CARD`, `IMEI`, `IP_ADDRESS`, `JOB_TITLE`, `JWT`, `LANGUAGE`, `LICENSE_NUMBER`, `LICENSE_PLATE`, `LOCATION`, `MAC_ADDRESS`, `MEDICAL_RECORD`, `NRP`, `ORGANIZATION`, `PASSPORT`, `PASSWORD`, `PERSON`, `PHONE_NUMBER`, `PIN`, `POLITICAL_VIEW`, `RACE_ETHNICITY`, `RELIGIOUS_BELIEF`, `SEXUALITY`, `TAX_ID`, `URL`, `UUID`, `VEHICLE_ID` `BE_ENTERPRISE_NUMBER`, `BE_NATIONAL_NUMBER` `DE_TAX_ID`, `DE_VAT_NUMBER` `FR_INSEE`, `FR_SIREN` `GB_NHS`, `GB_NINO` `BSN`, `KVK_NUMBER`, `NL_DRIVER_LICENSE`, `NL_ONDERWIJSNUMMER`, `NL_PAYROLL_TAX_ID`, `NL_RSIN` `MEDICAL_LICENSE`, `US_BANK_NUMBER`, `US_DRIVER_LICENSE`, `US_ITIN`, `US_PASSPORT`, `US_SSN` ### Entity type names that changed Three keys were renamed or dropped when the catalog became region-scoped. A stored `entities` list carrying an old key is now rejected at write time, so update it before configuring anything else: | Old key | Replacement | | ---------- | ---------------------- | | `JOBTITLE` | `JOB_TITLE` | | `UK_NHS` | `GB_NHS` | | `TITLE` | none: the type is gone | The US types keep their names but are now gated by the `us` region rather than by `language: en`, so a config that relied on English selecting them needs `regions: ["us"]` or an explicit `entities` list. ## Tracing Redaction is traced in-process as child spans of the request: `pii-redact` for the input pass and `pii-restore` for the output pass. Each span carries the configured `language`, the failure policy, the requested entity count, the placeholder count, and the outcome. `persist_redacted_to_traces` controls whether the redacted (placeholder) form or the restored original is stored in trace content; it defaults to `true` (redacted form stored). # Response Healing Source: https://docs.orq.ai/docs/ai-gateway/features/plugins/response-healing Repair malformed JSON in model output using the response_healing plugin in the AI Gateway. The `response_healing` **plugin** repairs malformed JSON in model output before the response reaches the caller. Models asked for structured output sometimes wrap it in a markdown code fence, add a sentence of explanation around it, or emit a trailing comma. The plugin extracts and repairs the JSON so the response parses. This feature is in Beta. ## Use cases * Getting reliable structured output from models with weaker JSON adherence. * Removing per-service JSON cleanup code from application clients. * Recovering tool calls whose arguments came back slightly malformed. ## Quick start Add a `response_healing` entry to the `plugins` array. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [{ "role": "user", "content": "List two colors as JSON." }], "response_format": { "type": "json_object" }, "plugins": [{ "id": "response_healing" }] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: 'https://my.orq.ai/v3/router', }); const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [{ role: 'user', content: 'List two colors as JSON.' }], response_format: { type: 'json_object' }, // @ts-ignore - orq.ai extension plugins: [{ id: 'response_healing' }], }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[{"role": "user", "content": "List two colors as JSON."}], response_format={"type": "json_object"}, extra_body={"plugins": [{"id": "response_healing"}]}, ) ``` The plugin takes no configuration. `response-healing`, the hyphenated spelling used by OpenRouter, is accepted as an alias for `response_healing`. Payloads migrated from OpenRouter work without edits. ## What gets repaired | Problem | Model output | Response | | ----------------------- | ---------------------------------------- | ------------ | | Markdown code fence | JSON wrapped in a ` ```json ` block | `{"a": 1}` | | JSON embedded in prose | `Here you go: {"a": 1} Hope that helps.` | `{"a": 1}` | | Missing closing bracket | `{"a": 1` | `{"a": 1}` | | Trailing comma | `{"a": 1,}` | `{"a": 1}` | | Unquoted keys | `{a: 1}` | `{"a": 1}` | | Single-quoted strings | `{'a': 'b'}` | `{"a": "b"}` | ## When it applies The plugin repairs two things: * **Message content**, only when the request asks for JSON. On `/v3/router/chat/completions` that means `response_format` is `json_object` or `json_schema`. On `/v3/router/responses` it means `text.format` declares `type: json_schema`, or carries a non-empty inline `schema` body without an explicit type. Requests that ask for plain text are never modified. * **Tool call arguments**, whenever the plugin is enabled. Arguments are JSON by definition, so no `response_format` is required. ## Limits * **Non-streaming requests only.** Streamed chunks reach the caller before the full body is known, so there is nothing left to repair. Setting the plugin on a streaming request has no effect. * **Output truncated by `max_tokens` may be unrecoverable.** A response cut mid-string cannot be reconstructed. * **Unrepairable output passes through unchanged.** The plugin never fails a request, and never replaces valid JSON. ## Enable for a workspace Enable response healing for every request from **Settings** > **Plugins**, without passing a `plugins` array on each call. Once enabled, it applies automatically to every call that doesn't already specify a `response_healing` plugin. ## Apply per routing rule Attach a `response_healing` entry to a [Routing Rule](/docs/ai-gateway/configuration/routing-rules) to run healing on the traffic that rule matches. ## Supported endpoints * [`POST /v3/router/responses`](/reference/responses/create-response) * [`POST /v3/router/chat/completions`](/reference/chat/create-chat-completion) # Prompt caching for reduced token costs Source: https://docs.orq.ai/docs/ai-gateway/features/prompt-caching Cache repeated prompt prefixes at the provider level to reduce input token costs and latency. Supported on Anthropic, OpenAI, and Google Gemini. **Use Cases** * Reusing long system prompts across many requests to cut input token costs. * Referencing large documents or codebases without re-sending them every call. * Multi-turn conversations with a large, stable context that doesn't change between turns. * RAG pipelines where the same retrieved context is shared across many user queries. *** ## Overview Prompt Caching is a provider-level feature that caches prompts so that **repeated requests** are charged at a reduced rate. This is most effective when your requests share a **large, stable prefix**: * a long system prompt. * a reference document. * a tool definition list. Unlike [Response Caching](/docs/ai-gateway/features/cache), which serves a stored response for identical requests, Prompt Caching still calls the model on every request, at a reduced cost. Both can be used together. How caching is enabled and what gets cached varies by provider. See the provider sections below. ## Anthropic Prompt caching on Anthropic models requires explicit opt-in via `cache_control` markers on individual message parts. A breakpoint marks the end of a cacheable prefix: the provider caches everything up to and including the marked block, and later requests that share that prefix read the cached portion instead of reprocessing it. On the Anthropic-compatible endpoint, breakpoints pass through exactly as in the native Anthropic API; see the [Anthropic Messages API](/docs/ai-gateway/features/anthropic-messages-api) for endpoint details and a multi-turn example. ### Supported models All current Claude models, including Claude Fable 5 and the Opus, Sonnet, and Haiku families. ### Enabling caching Add a `cache_control` object to any message part you want to mark as cacheable: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "cache_control": { "type": "ephemeral" } } ``` `"ephemeral"` is the only supported type. You can place it on: * System message text parts. * User message text parts. * User message images, documents, and files (including PDFs). * Tool definitions. * Tool result content. ### Minimum token thresholds Caching only activates once the marked content exceeds a minimum token count. Requests below the threshold are processed normally at full cost. | Model | Minimum tokens | | ----------------------------------------------------------------------------------------- | -------------- | | Claude Opus 5, Fable 5 | 512 | | Claude Opus 4.8, Sonnet 5, Sonnet 4.6, Sonnet 4.5, Opus 4.1, Opus 4, Sonnet 4, Sonnet 3.7 | 1,024 | | Claude Opus 4.7, Haiku 3.5, Haiku 3 | 2,048 | | Claude Opus 4.6, Opus 4.5, Haiku 4.5 | 4,096 | ### Cache TTL The `ttl` parameter controls how long cached content persists before expiring. | Value | Duration | | ---------------- | ----------------------- | | `"5m"` (default) | 5 minutes from last use | | `"1h"` | 1 hour | ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "cache_control": { "type": "ephemeral", "ttl": "1h" } } ``` Cache reads are billed at a fraction of the regular input token price. Cache writes carry a premium: 1.25x the input price for the 5-minute TTL and 2x for the 1-hour TTL. Caching pays off from the second request onward on a shared prefix. ### Multi-turn conversations **Default: mark the system prompt.** Put `cache_control` on the system text part and send the full input on every request, including any conversation history the client keeps. The system prompt is written once and read on every later turn, no matter what the user says. Every other example on this page uses this form. **When continuing with `previous_response_id`, use the top-level parameter instead.** The gateway rebuilds the history from storage, and block-level markers from earlier turns are not carried over. Send only the new user message and set `cache_control` at the top level of the request. Each turn then reads the whole previous conversation and writes only the new message. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "model": "anthropic/claude-sonnet-5", "previous_response_id": "resp_...", "cache_control": { "type": "ephemeral" }, "input": [ { "role": "user", "content": "And what about clause 9?" } ] } ``` Do not resend the system prompt on a `previous_response_id` turn. The stored history already contains it, and a second copy fails validation on Anthropic models. Avoid the top-level parameter on independent requests. It marks the last block, which is the user message, so requests with different questions never share a prefix and each one pays the cache write premium without a read. ### Example ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-5", "input": [ { "role": "system", "content": [ { "type": "input_text", "text": "You are a senior legal assistant. The following is our complete contract template library...", "cache_control": { "type": "ephemeral" } } ] }, { "role": "user", "content": "Summarize clause 7 of the NDA template." } ] }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-5", "messages": [ { "role": "system", "content": [ { "type": "text", "text": "You are a senior legal assistant. The following is our complete contract template library...", "cache_control": { "type": "ephemeral" } } ] }, { "role": "user", "content": "Summarize clause 7 of the NDA template." } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "anthropic/claude-sonnet-5", input: [ { role: "system", content: [ { type: "input_text", text: "You are a senior legal assistant. The following is our complete contract template library...", cache_control: { type: "ephemeral" }, }, ], }, { role: "user", content: "Summarize clause 7 of the NDA template." }, ], }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="anthropic/claude-sonnet-5", input=[ { "role": "system", "content": [ { "type": "input_text", "text": "You are a senior legal assistant. The following is our complete contract template library...", "cache_control": {"type": "ephemeral"}, } ], }, {"role": "user", "content": "Summarize clause 7 of the NDA template."}, ], ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "anthropic/claude-sonnet-5", messages: [ { role: "system", content: [ { type: "text", text: "You are a senior legal assistant. The following is our complete contract template library...", cache_control: { type: "ephemeral" }, }, ], }, { role: "user", content: "Summarize clause 7 of the NDA template." }, ], }); console.log(response.choices[0].message.content); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="anthropic/claude-sonnet-5", messages=[ { "role": "system", "content": [ { "type": "text", "text": "You are a senior legal assistant. The following is our complete contract template library...", "cache_control": {"type": "ephemeral"}, } ], }, {"role": "user", "content": "Summarize clause 7 of the NDA template."}, ], ) print(response.choices[0].message.content) ``` ## OpenAI Prompt caching on OpenAI models is **fully automatic**. No `cache_control` or any request changes are required. The **AI Gateway** forwards requests normally; OpenAI caches the prompt prefix on its side and applies the discount transparently. Caching activates on prompts longer than 1,024 tokens, in 128-token increments from that threshold. The API caches the longest matching prefix from prior requests on the same machine. Cache retention duration is model-dependent and determined by OpenAI. Refer to [OpenAI's prompt caching documentation](https://platform.openai.com/docs/guides/prompt-caching) for the current retention policy per model. Cache hits are reflected in the response `usage` object. See [Usage in the response](#usage-in-the-response) below. Set up your OpenAI API key to use GPT models with automatic prompt caching. ## Google Gemini Google Gemini supports two caching modes through the **AI Gateway**. **Implicit caching** is enabled by default on Gemini 2.5 and newer models. No request changes are needed. The **AI Gateway** forwards requests normally and Google applies the cache discount automatically when a matching prefix exists. Implicit caching activates at a model-dependent minimum: 2,048 tokens on Gemini 2.5 models, higher on newer generations. Verify current thresholds and rates in [Google's caching documentation](https://ai.google.dev/gemini-api/docs/caching). **Explicit caching** is managed by the **AI Gateway** on supported Gemini models (Gemini 2.5 and newer). Add `cache_control` markers to system or user message parts (text and files), using the same syntax as on Anthropic models. The gateway creates a Google cache object for the marked content, reuses it on subsequent requests that mark the same content, and lets it expire after the TTL (`5m` by default, `1h` supported). There is no need to create or reference Google cache objects directly. Set up your Google AI API key to use Gemini models with implicit prompt caching. ## Usage in the response Every endpoint reports cache reads and cache writes in the `usage` object. Field names follow the endpoint's own response shape, so the same request looks slightly different on each API. ### Responses API On `/v3/router/responses`, cache activity is reported under `usage.input_tokens_details`. The same fields appear in the final `response.completed` event when streaming. | Field | Meaning | | -------------------------- | ---------------------------------------------------------------------------- | | `cached_tokens` | Tokens read from the cache. | | `cache_creation_tokens` | Tokens written to the cache, across all TTLs. | | `cache_write_tokens` | Same value as `cache_creation_tokens`, kept for compatibility. | | `cache_creation_5m_tokens` | Tokens written with the 5-minute TTL. Present only on a write with that TTL. | | `cache_creation_1h_tokens` | Tokens written with the 1-hour TTL. Present only on a write with that TTL. | A first request on a new prefix writes the cache: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "usage": { "input_tokens": 2673, "output_tokens": 30, "total_tokens": 2703, "input_tokens_details": { "cached_tokens": 0, "cache_creation_tokens": 2669, "cache_write_tokens": 2669, "cache_creation_5m_tokens": 2669 } } } ``` A later request on the same prefix reads it. The per-TTL fields are omitted when nothing was written: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "usage": { "input_tokens": 2673, "output_tokens": 30, "total_tokens": 2703, "input_tokens_details": { "cached_tokens": 2669, "cache_creation_tokens": 0, "cache_write_tokens": 0 } } } ``` `input_tokens` includes cached tokens, matching the OpenAI convention. ### Chat Completions API On `/v3/router/chat/completions`, cache activity is reported under `usage.prompt_tokens_details`. The per-TTL split is not available on this endpoint. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "usage": { "prompt_tokens": 2673, "completion_tokens": 30, "total_tokens": 2703, "prompt_tokens_details": { "cached_tokens": 2669, "cache_creation_tokens": 0, "audio_tokens": 0 } } } ``` ### Anthropic Messages API On `/v3/anthropic/v1/messages`, usage uses the native Anthropic field names. `input_tokens` counts only uncached tokens. Cache fields are present only when non-zero: a write omits `cache_read_input_tokens`, and a pure read omits `cache_creation_input_tokens` and the nested `cache_creation` object. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "usage": { "input_tokens": 4, "output_tokens": 30, "cache_creation_input_tokens": 2669, "cache_creation": { "ephemeral_5m_input_tokens": 2669, "ephemeral_1h_input_tokens": 0 } } } ``` ## Cache usage in Traces Cache reads and writes are recorded on every LLM span, so they can be inspected per request in [Traces](/docs/ai-studio/observability/traces) and aggregated in Analytics. | Where | What is shown | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Span attributes | `gen_ai.usage.prompt_tokens_details.cached_tokens`, `gen_ai.usage.prompt_tokens_details.cache_creation_tokens`, and the per-TTL `gen_ai.usage.prompt_tokens_details.cache_creation_5m_tokens` / `gen_ai.usage.prompt_tokens_details.cache_creation_1h_tokens`. | | Economics panel and trace list | Cached input tokens, plus `orq.billing.cache_read_cost` and `orq.billing.cache_write_cost` in USD. The write cost already includes the 1.25x or 2x TTL premium. | | Analytics | Cached tokens and cache read/write cost roll up into the workspace token and cost breakdowns. | The per-TTL token split is stored as a span attribute only. It is not surfaced as a separate column in the trace list or in Analytics. See [Span attributes](/docs/ai-studio/observability/span-attributes#token-usage) for the token usage and billing attributes and [Token and cost tracking](/docs/ai-studio/observability/token-cost-tracking) for how cache costs are computed. # Rate limits and quotas Source: https://docs.orq.ai/docs/ai-gateway/features/rate-limits Understand how the AI Gateway enforces plan-based request limits, budget limits, and what a 429 looks like. **Orq.ai** enforces limits at three layers. Every request to the **AI Gateway** passes a plan-based platform rate limit, any **Budget** limits that match the request, and finally the upstream provider's own quotas. Knowing which layer rejected a request is the first step to fixing it. | Layer | Enforced by | Configurable | Applies to | | -------------------- | ----------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------- | | Platform rate limits | **Orq.ai**, per workspace plan | Custom plans by arrangement | Every request to the gateway | | Budget limits | **Orq.ai**, from configured budgets | Yes, under **Settings > Organization > [Budgets](/docs/ai-gateway/budgets)** | Requests matching the budget scope | | Provider rate limits | The upstream provider account | By the provider | Requests routed to that provider | ## Platform rate limits Each workspace is limited to a fixed number of requests per minute based on its subscription. When the limit is reached, requests are rejected with `429 Too Many Requests` until the rolling 60-second window resets. | Plan | Requests per minute | Requests per day | | ------------- | --------------------------- | ---------------- | | Free | 20 | 50 | | Pay as you go | 100 | No cap | | Custom | 1500, higher by arrangement | No cap | * Limits use a **rolling 60-second window**, the same window that applies to budget requests-per-minute limits. * The daily cap applies to the Free plan only. * Custom plans can arrange higher per-minute limits with **Orq.ai**. * On-premise deployments are licensed rather than metered: plan-based limits do not apply. A single system-wide cap can be enforced with the `ORQ_SYSTEM_RPM_LIMIT` environment variable. Platform limit rejections return `429` with a `Retry-After` header and the `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. ## Budget limits [Budgets](/docs/ai-gateway/budgets) apply configured limits to a named target. A budget defines one or more limits: **Cost** in USD, **Tokens**, and **Requests per minute**. See [Budgets](/docs/ai-gateway/budgets) for how to configure them. Requests per minute always uses a rolling 60-second window, **independent of the reset period**. A monthly budget's `50 req/min` still allows 50 requests in any single minute and resets its request counter 60 seconds after the first request in the window, not at month end. The reset period (Daily, Weekly, Monthly, Yearly, One-time) applies to cost and token limits only. ### Limits on an API key API keys do not carry limit fields of their own. Set cost, token, or requests-per-minute limits on a key by creating a [Budget](/docs/ai-gateway/budgets) scoped to that **API Key**. The key's remaining capacity is visible in the budget's detail page, and its cost and token capacity in the `X-RateLimit-*-Cost` and `X-RateLimit-*-Tokens` response headers. The unsuffixed `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers report the plan's platform rate limit on a successful request, not a budget's requests-per-minute limit, because the platform limiter writes them last. A budget's requests-per-minute values appear in these headers only on a `429` raised by that budget. Read budget request consumption from the budget's detail page instead. Legacy API keys, created before the current unified key model, do not support budget-based cost, token, or rate limits. Create a current API key and scope a [Budget](/docs/ai-gateway/budgets) to it instead. ## Multiple limits on one request A request can match several budgets at once: for example, a budget scoped to the workspace and another scoped to the calling API key. Every matching budget is enforced, and each tracks its own usage. The most restrictive applicable limit binds. See [Budget levels and interaction](/docs/ai-gateway/budgets#budget-levels-and-interaction) for the full rules and worked examples. ## The 429 response When any limit is exceeded, the gateway returns `429 Too Many Requests` with an OpenAI-compatible error body. Budget rejections carry `scope_kind`, `scope_target_id`, and `dimension`: ```json Budget rejection (requests per minute) theme={"theme":{"light":"github-light","dark":"github-dark"}} { "error": { "message": "Rate limit exceeded. Maximum requests allowed per minute.", "type": "rate_limit_error", "param": null, "code": "requests_per_minute_exceeded", "scope_kind": "workspace", "scope_target_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "dimension": "requests" } } ``` The `code` and `dimension` identify the exceeded limit: | Code | Dimension | Limit | | ------------------------------ | ---------- | --------------------------------------- | | `rate_limit_exceeded` | (absent) | Platform rate limit (subscription plan) | | `requests_per_minute_exceeded` | `requests` | Budget requests per minute | | `cost_budget_exceeded` | `cost` | Budget cost | | `token_budget_exceeded` | `tokens` | Budget tokens | `scope_kind` and `scope_target_id` identify which budget rejected the request and are present only on budget rejections. The `message` text varies by limit type; use the `code` field for programmatic matching. ### Rate limit headers Rate limit headers are emitted per dimension, and each group of three is present only when that dimension applies to the request. A response therefore carries some of these headers, not all of them. Headers appear on successful requests (showing remaining capacity) as well as on rejections. | Header | Meaning | When present | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | | `X-RateLimit-Limit` | Requests per minute allowed by the plan's platform rate limit | On every gateway request | | `X-RateLimit-Remaining` | Requests left in the current window | On every gateway request | | `X-RateLimit-Reset` | Seconds until the window resets (e.g. `42s`) | On every gateway request | | `X-RateLimit-Limit-Daily` | Daily request limit | Free plan only | | `X-RateLimit-Remaining-Daily` | Requests left in the daily window | Free plan only | | `X-RateLimit-Reset-Daily` | Seconds until the daily window resets (e.g. `42s`) | Free plan only | | `X-RateLimit-Limit-Tokens` | Token limit | Only when a matching budget sets a token limit | | `X-RateLimit-Remaining-Tokens` | Tokens left in the current period | Only when a matching budget sets a token limit | | `X-RateLimit-Reset-Tokens` | Seconds until the token counter resets (e.g. `42s`) | Only when a matching budget sets a token limit | | `X-RateLimit-Limit-Cost` | Cost limit in USD | Only when a matching budget sets a cost limit | | `X-RateLimit-Remaining-Cost` | Cost remaining in the current period (in USD) | Only when a matching budget sets a cost limit | | `X-RateLimit-Reset-Cost` | Seconds until the cost counter resets (e.g. `42s`) | Only when a matching budget sets a cost limit | | `Retry-After` | Seconds until the rejecting limit resets, as a plain integer (e.g. `42`). The `X-RateLimit-Reset*` headers use the `42s` format instead | Rejections only | Two cases drop headers that the table otherwise lists as always present. The unsuffixed `X-RateLimit-*` triplet is omitted when the request carries no workspace context, and in on-premise deployments unless `ORQ_SYSTEM_RPM_LIMIT` is set. `Retry-After` is omitted when a rejected budget never resets on a clock, such as a **One-time** budget. ### Retries and fallbacks `429` is retryable. The gateway's [Retries](/docs/ai-gateway/features/retries) feature retries on `429` by default with exponential backoff and honors the `Retry-After` header, and [fallbacks](/docs/ai-gateway/features/retries#fallbacks) route to a backup model when the primary is rate-limited. Retries run inside the gateway on a single incoming request (the platform rate limiter counts inbound client requests, not upstream calls), so they do not consume additional platform or budget quota. ## See also Create budgets with cost, token, and requests-per-minute limits and configure alerts. How budgets at different levels interact when several apply to the same request. Create and manage **AI Gateway** API keys, then scope a budget to cap usage. Retry rate-limited requests with backoff and route to fallback models. # Reasoning models Source: https://docs.orq.ai/docs/ai-gateway/features/reasoning Use GPT-5.6 Sol, Claude Opus 5, and other reasoning models through the AI Gateway. Reasoning and thinking models perform internal deliberation before generating a response. Each provider exposes this differently: OpenAI uses `reasoning_effort`, while Google Gemini and Anthropic use a `thinking` object. The **AI Gateway** accepts all three controls and normalizes values to what each model actually supports before forwarding the request. | Provider | Control | Values | | ----------------------- | ------------------------- | --------------------------------------------------- | | OpenAI o-series | `reasoning_effort` | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` | | Google Gemini 3 preview | `thinking.thinking_level` | `low`, `high` | | Google Gemini 2.5 | `thinking.budget_tokens` | integer | | Anthropic Claude | `thinking.budget_tokens` | integer | **Use Cases** * Problems requiring multi-step logical deduction (math proofs, code debugging, planning). * Complex analysis where a standard model produces shallow or incorrect results. * Research tasks where depth of reasoning matters more than response speed. * Benchmarking reasoning quality across providers on identical prompts. *** ## Quick Start The **AI Gateway** supports three reasoning controls: * `reasoning` object on `POST /responses` for OpenAI reasoning models. * `reasoning_effort` on `POST /chat/completions` for OpenAI reasoning models. * `thinking` on `POST /chat/completions` for Google Gemini and Anthropic extended thinking. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Analyze the logical flaw in this argument.", "reasoning": {"effort": "medium"} }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: 'https://my.orq.ai/v3/router', }); const response = await client.responses.create({ model: 'openai/gpt-5.4-mini', input: 'Analyze the logical flaw in this argument.', reasoning: { effort: 'medium' }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Analyze the logical flaw in this argument.", extra_body={"reasoning": {"effort": "medium"}}, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: 'https://my.orq.ai/v3/router', }); const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [{ role: 'user', content: 'Analyze the logical flaw in this argument.' }], reasoning_effort: 'medium', }); console.log(response.choices[0].message.content ?? ""); const geminiLevel = await client.chat.completions.create({ model: 'google/gemini-2.5-pro', messages: [{ role: 'user', content: 'Plan a 3-day Tokyo itinerary under $500.' }], thinking: { type: 'enabled', budget_tokens: 4096 }, }); console.log(geminiLevel.choices[0].message.content ?? ""); const anthropicThinking = await client.chat.completions.create({ model: 'anthropic/claude-sonnet-5', messages: [{ role: 'user', content: 'Design a rate limiting strategy for a global API.' }], thinking: { type: 'enabled', budget_tokens: 4096 }, max_tokens: 8000, }); console.log(anthropicThinking.choices[0].message.content ?? ""); ``` ## Request Fields | Field | Type | Values | Notes | | ------------------------- | ------ | --------------------------------------------------- | -------------------------------------------------- | | `reasoning_effort` | string | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` | OpenAI-style reasoning control | | `thinking.type` | string | `enabled`, `disabled` | Used by Google Gemini and Anthropic thinking paths | | `thinking.budget_tokens` | number | integer | Budget-based thinking | | `thinking.thinking_level` | string | `low`, `high` | Level-based thinking for Gemini 3 preview models | Treat `thinking.budget_tokens` and `thinking.thinking_level` as mutually exclusive. On the Google path, if `thinking_level` is present it takes precedence over `budget_tokens`. ## Provider Behavior ### OpenAI reasoning models Use `reasoning_effort` on `POST /chat/completions`. Current registry examples: * `openai/gpt-5.6-sol`. * `openai/gpt-5.4-mini`. The **AI Gateway** schema accepts all six enum values, but model support is ultimately model-specific. The router normalizes `reasoning_effort` to the nearest value a model supports before forwarding the request. For example, `openai/gpt-5.4` does not support `xhigh`: it maps to `high`. Models that do support `xhigh` receive the value as-is. The **AI Gateway** automatically drops `temperature` and `top_p` before forwarding the request whenever the model reasons. For OpenAI and Azure reasoning models, that covers any `reasoning_effort` other than `none` and the case where `reasoning_effort` is not set at all, since these models reason by default. Set `reasoning_effort: none` to forward sampling parameters. These parameters are incompatible with OpenAI reasoning models and will cause an error if sent directly. Set up the OpenAI API key and explore all supported models including the GPT-5.x reasoning family. ### Google Gemini Use the `thinking` object. Level-based (`thinking_level`) examples: * `google/gemini-3-flash-preview`. * `google/gemini-3.1-pro-preview`. Budget-based (`budget_tokens`) examples: * `google/gemini-2.5-flash`. * `google/gemini-2.5-flash-lite`. * `google/gemini-2.5-pro`. Router behavior: * `thinking: { "type": "disabled" }` is valid * On `thinking_enforced` models such as `google/gemini-2.5-pro`, disabling thinking is coerced to a minimum budget of `128` * On non-enforced Gemini models, disabling thinking becomes a budget of `0` Set up the Google AI API key and explore Gemini 2.5 and Gemini 3 thinking models. ### Anthropic Claude On `POST /chat/completions`, Anthropic uses `thinking: { type, budget_tokens }`. Current registry examples: * `anthropic/claude-sonnet-5`. * `anthropic/claude-opus-5`. Router behavior: * Anthropic chat completions only forward thinking when `type` is `enabled`. * `budget_tokens` must be greater than `0` to be forwarded. * `thinking_level` is not used for Anthropic chat completions. Set up the Anthropic API key and explore Claude extended thinking capabilities. ## Responses API `POST /responses` supports reasoning for **OpenAI models only**. Use the OpenAI-style `reasoning` object with `effort` instead of `reasoning_effort`. `thinking` (Anthropic and Google Gemini) is not supported on the `/responses` endpoint. Use `POST /chat/completions` for Anthropic and Google reasoning models. ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "model": "openai/gpt-5.4-mini", "input": "Solve this step by step.", "reasoning": { "effort": "medium" } } ``` ## Usage and Output Reasoning token usage is returned under `usage.completion_tokens_details.reasoning_tokens`. ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "usage": { "prompt_tokens": 120, "completion_tokens": 980, "total_tokens": 1100, "completion_tokens_details": { "reasoning_tokens": 640 } } } ``` Anthropic's API does not always report thinking tokens separately. When it does not, `reasoning_tokens` is `0` and thinking tokens are included in the total `completion_tokens` count. Google Gemini and OpenAI reasoning models typically report `reasoning_tokens` correctly. ### Streaming reasoning chunks In streaming mode, thinking models deliver chain-of-thought content as a `reasoning` field on chunks that carry reasoning content. Text-only chunks do not include this field. The `reasoning` streaming field is available for both Anthropic and Google Gemini thinking models. OpenAI reasoning models stream thinking content through `reasoning_summary_text.delta` events instead. Do not rely on this field being present across all providers. Do not rely on visible chain-of-thought text being present in every response. The stable contract is the request fields above plus token usage. Provider-specific fields such as `reasoning`, `reasoning_signature`, or `redacted_reasoning` may appear, but they are optional. ## Code Examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Solve this step by step: What is 15% of 250?", "reasoning": {"effort": "medium"} }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-opus-5", "messages": [ { "role": "user", "content": "Break down the tradeoffs of event-driven vs request-response systems." } ], "thinking": { "type": "enabled", "budget_tokens": 8192 }, "max_tokens": 16000 }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: 'https://my.orq.ai/v3/router', }); const response = await client.responses.create({ model: 'openai/gpt-5.4-mini', input: 'Solve this step by step: What is 15% of 250?', reasoning: { effort: 'medium' }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Solve this step by step: What is 15% of 250?", extra_body={"reasoning": {"effort": "medium"}}, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions: Anthropic thinking) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: 'https://my.orq.ai/v3/router', }); const response = await client.chat.completions.create({ model: 'anthropic/claude-opus-5', messages: [ { role: 'user', content: 'Break down the tradeoffs of event-driven vs request-response systems.', }, ], thinking: { type: 'enabled', budget_tokens: 8192 }, max_tokens: 16000, }); console.log(response.choices[0].message.content ?? ""); ``` ```python Python (Chat Completions: Anthropic thinking) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="anthropic/claude-opus-5", messages=[ { "role": "user", "content": "Break down the tradeoffs of event-driven vs request-response systems.", } ], max_tokens=16000, extra_body={ "thinking": { "type": "enabled", "budget_tokens": 8192, } }, ) print(response.choices[0].message.content or "") ``` ## Choosing a Setting Use `reasoning_effort` when the model is in an OpenAI reasoning family such as `gpt-5.x`, `o1`, or `o3`. Use `thinking_level` for Gemini 3 preview models. Use `budget_tokens` for Anthropic and budget-based Gemini models. For the current model catalog, see [Supported Models](/docs/ai-gateway/supported-models). # Rerank and Moderations Source: https://docs.orq.ai/docs/ai-gateway/features/rerank-and-moderations Rerank documents by relevance to a query and moderate text against safety categories through the AI Gateway. **Use Cases** * Reordering results from an initial search so the most relevant documents appear first. * Improving RAG answer quality by scoring each document directly against the query. * Screening user input or generated output for harmful content before it is shown. *** ## Rerank ### Overview The **AI Gateway** exposes `POST /rerank` on the [OpenAI-compatible API](/docs/ai-gateway/features/openai-compatible-api) base URL (`https://my.orq.ai/v3/router`). Send a `query` and a list of `documents`, and receive the documents ordered by relevance to the query. Reranking re-orders the results of an initial retrieval (for example a vector or keyword search) with a cross-encoder model, a model that reads the query and each document together. This is slower but more accurate than comparing embeddings alone. The request requires three fields: `query`, `documents`, and `model`. Send no more than 1,000 documents in a single request. ### Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/rerank \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "cohere/rerank-english-v3.0", "query": "What is the capital of France?", "documents": [ "Paris is the capital of France.", "Berlin is the capital of Germany.", "London is the capital of the United Kingdom." ] }' ``` ```typescript TypeScript (orq SDK) theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_API_KEY ?? "", }); const result = await orq.router.rerank.create({ model: "cohere/rerank-english-v3.0", query: "What is the capital of France?", documents: [ "Paris is the capital of France.", "Berlin is the capital of Germany.", "London is the capital of the United Kingdom.", ], }); console.log(result.results); ``` ```python Python (orq SDK) theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ.get("ORQ_API_KEY", "")) result = orq.router.rerank.create( model="cohere/rerank-english-v3.0", query="What is the capital of France?", documents=[ "Paris is the capital of France.", "Berlin is the capital of Germany.", "London is the capital of the United Kingdom.", ], ) print(result.results) ``` See [Supported Models](/docs/ai-gateway/supported-models) for the rerank models available through the gateway. ### Response Each entry in the `results` array contains: | Field | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------- | | `index` | Position of the document in the original `documents` array | | `relevance_score` | Relevance of the document to the query, normalized to the range `[0, 1]`. Scores close to 1 indicate high relevance. | | `document` | Optional. The original document text, containing a `text` field. Returned only when `return_documents` is `true`. | Results are ordered by `relevance_score` descending, so the most relevant document is first. Pass `top_n` to limit the number of results returned; it defaults to the full length of `documents`. Pass `return_documents: true` to include the original document text in each result. ### Rerank and Knowledge Base Search [Knowledge Base search](/docs/ai-gateway/features/knowledge-bases) already supports reranking: the `search-knowledge-base` endpoint accepts a `rerank_config` object and returns a `rerank_score` alongside the `search_score` for every retrieved chunk. `rerank_config` fields: * **`model`**: The rerank model to use * **`top_k`**: Number of top results to return after reranking; defaults to the Knowledge Base `top_k` * **`threshold`**: Only return documents with a relevance score above this value; defaults to `0` Where reranking runs: * **Inside a Knowledge Base**: pass `rerank_config` when retrieval stays inside the gateway. * **Directly via `POST /rerank`**: call the endpoint when the initial candidates come from an external store, for example a third-party vector database, and only the final ordering should happen in the gateway. ## Moderations ### Overview The **AI Gateway** exposes `POST /moderations` on the [OpenAI-compatible API](/docs/ai-gateway/features/openai-compatible-api) base URL (`https://my.orq.ai/v3/router`). Send text and receive safety classifications: for each input, the response reports whether it is `flagged` and, per category, a boolean `categories` verdict and a `category_scores` confidence score. The request requires `input`; `model` defaults to `openai/omni-moderation-latest`. Other available models include `mistral/mistral-moderation-2603`. ### Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/moderations \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/omni-moderation-latest", "input": "I want to hurt someone" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const moderation = await client.moderations.create({ model: "openai/omni-moderation-latest", input: "I want to hurt someone", }); console.log(moderation.results[0]); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) moderation = client.moderations.create( model="openai/omni-moderation-latest", input="I want to hurt someone", ) print(moderation.results[0]) ``` ### Response Each entry in the `results` array corresponds to one input and contains: | Field | Description | | ----------------- | ----------------------------------- | | `flagged` | `true` when any category is flagged | | `categories` | Boolean verdict per category | | `category_scores` | Confidence score per category | Categories include `hate`, `harassment`, `illicit`, `self-harm`, `sexual`, `violence`, and their subcategories such as `hate/threatening` and `self-harm/intent`. Use `flagged` or the individual category scores to decide what the application does, for example blocking output above a `violence` score threshold. Category names and scores vary by model. OpenAI-compatible models return the 13-category set above. Mistral models (`mistral/mistral-moderation-*`) return their own set: `sexual`, `hate_and_discrimination`, `violence_and_threats`, `dangerous_and_criminal_content`, `selfharm`, `health`, `financial`, `law`, `pii`. ### Moderations vs Guardrails Moderations is a **scoring endpoint**: it classifies text and returns scores. It does not block anything, and the caller decides what to do with the result. [Guardrails](/docs/ai-gateway/configuration/guardrails) are the **enforcement** layer: LLM-as-a-Judge or Python evaluators attached to [Guardrail Rules](/docs/ai-gateway/configuration/guardrail-rules) that block non-compliant requests and responses automatically, before they reach the caller. Use moderations when the application inspects or acts on the scores itself, and guardrails when the gateway should enforce the policy without application code. # Responses API Source: https://docs.orq.ai/docs/ai-gateway/features/responses-api Create model responses with built-in tools, server-side state, and multimodal input through the AI Gateway. **Use Cases** Use web search and other built-in tools without client-side tool orchestration. Server-side state is retained between turns via `previous_response_id`, without resending the full message history. Pass `input_image` and `input_file` items in a single request. Combine function calling, reasoning, and tool continuation for agent-style loops. *** The **Responses API** is the OpenAI-style `/responses` endpoint on the **AI Gateway**, exposed at [`POST /v3/router/responses`](/reference/responses/create-response). It implements the [OpenResponses](https://www.openresponses.org/) specification: a request carries a `model` and an `input`, and the response returns structured `output` items together with token and cost usage. The endpoint supports built-in tools such as web search, server-side conversation state, streaming, and multimodal input. Choose it when those capabilities matter; for a classic messages-based flow, use [Chat Completions](/docs/ai-gateway/features/openai-compatible-api). Invoke a configured agent by setting `model` to `agent/`; the agent's tools, knowledge bases, and memory apply automatically. See [Run Agents](/docs/ai-studio/ai-engineering/run-agents). ## Responses vs Chat Completions Both endpoints share the same base URL (`https://my.orq.ai/v3/router`), authentication, and **AI Gateway** features: [fallbacks](/docs/ai-gateway/features/retries#fallbacks), [retries](/docs/ai-gateway/features/retries), [caching](/docs/ai-gateway/features/cache), [guardrails](/docs/ai-gateway/configuration/guardrails), and [budgets](/docs/ai-gateway/budgets). The table below lists the differences. | Consideration | Responses API ([`POST /v3/router/responses`](/reference/responses/create-response)) | Chat Completions ([`POST /v3/router/chat/completions`](/reference/chat/create-chat-completion)) | | ------------------ | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Built-in tools | Supported. Web search (`web_search`, `web_search_preview`) is Responses-only | Not supported | | Conversation state | Server-side: `previous_response_id` continues from a stored response (`store` defaults to `true`) | Client-side: resend the full message history each turn | | Multimodal input | `input` items: `input_text`, `input_image`, `input_file` | `messages` content: `text`, `image_url`, `file` | | Reasoning controls | [`reasoning` object](/docs/ai-gateway/features/reasoning) for OpenAI models | `reasoning_effort` (OpenAI) and `thinking` (Anthropic, Google Gemini) | | Streaming events | `response.output_text.delta` and other response events | `choices[0].delta.content` | | Output shape | Item-based `output` array with the `output_text` helper | `choices[0].message` | Function calling works on both endpoints through the `tools` array; see [Tool Calling](/docs/ai-gateway/features/tool-calling). ## Quick Start Use the OpenAI SDK against the **AI Gateway** base URL and call `client.responses.create`. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "What is the capital of France?" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "What is the capital of France?", }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.responses.create( model="openai/gpt-5.6-sol", input="What is the capital of France?", ) print(response.output_text) ``` ## Statefulness Responses are persisted server-side by default (`store` defaults to `true`) and can be retrieved by ID (`GET /v3/router/responses/{response_id}`, see [Retrieve Response](/reference/responses/retrieve-response)). Continue a conversation by passing `previous_response_id` on the next request; the gateway uses the stored conversation instead of requiring the full history again. `previous_response_id` requires `store: true` on the original response. Set `store: false` to skip persisting a response. The response cannot be retrieved later, and `previous_response_id` will not work on follow-up requests. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} # First turn. The response ID is returned as "id" in the response body. curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "My name is Ada and I am writing a book about AI." }' # Continue the conversation from the stored response. curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "previous_response_id": "resp_01KP6DFXWPKZ12AS254R4W6C08", "input": "What is the title of my book?" }' # Retrieve the stored response by ID. curl https://my.orq.ai/v3/router/responses/resp_01KP6DFXWPKZ12AS254R4W6C08 \ -H "Authorization: Bearer $ORQ_API_KEY" ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const first = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "My name is Ada and I am writing a book about AI.", }); const followUp = await client.responses.create({ model: "openai/gpt-5.6-sol", previous_response_id: first.id, input: "What is the title of my book?", }); const retrieved = await client.responses.retrieve(first.id); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) first = client.responses.create( model="openai/gpt-5.6-sol", input="My name is Ada and I am writing a book about AI.", ) follow_up = client.responses.create( model="openai/gpt-5.6-sol", previous_response_id=first.id, input="What is the title of my book?", ) retrieved = client.responses.retrieve(first.id) ``` ## Streaming Set `"stream": true` on the request body. The server responds with a Server-Sent Events stream in the OpenAI Responses format: incremental text arrives as `response.output_text.delta` events, and the stream ends with a completed event carrying final usage. See [Streaming](/docs/ai-gateway/features/streaming) for event handling, retry, and error patterns. ## Telemetry Successful responses carry the OpenTelemetry ids of the turn. The response body includes a top-level `telemetry` object with the trace and span ids of the request: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "id": "resp_01KP6GDNJY5B0TT0R35KS23PYV", "status": "completed", "telemetry": { "trace_id": "9f2c1b3a4d5e6f708192a3b4c5d6e7f8", "span_id": "1a2b3c4d5e6f7081" } } ``` `trace_id` is the 32-character hex id of the trace and `span_id` the 16-character hex id of the response span. The same values are echoed as the `x-orq-trace-id` and `x-orq-trace-span-id` response headers, which every router endpoint sets. The `telemetry` object is present only on successful calls. Failed calls do not include it in the response body: read the ids from the `x-orq-trace-id` and `x-orq-trace-span-id` response headers instead, which are set on error responses as well. When streaming, the ids arrive on the final `response.completed` event inside `response.telemetry`; the `response.created` event does not carry them. Read them from the completed event, or from the response headers. Use the ids to correlate the call with its [Trace](/docs/ai-studio/observability/traces) and wherever a span must be referenced, such as [annotating a span](/reference/annotations/annotate-a-span) or [logging feedback](/reference/feedback/post-v2feedback). ## Built-in tools Built-in tools run server-side: pass them in the `tools` array and the gateway executes them during generation. Web search is available only through the Responses API, as `web_search` and `web_search_preview` tool types. See [Web search in Responses API](/docs/ai-gateway/features/web-search) for tool fields, provider mapping, and the `include` option. Function tools work on both endpoints; see [Tool Calling](/docs/ai-gateway/features/tool-calling). ## Multimodal input Send images and PDFs alongside text in the `input` array using `input_image` and `input_file` items. See [Image, PDF, and audio: multimodal inputs and generation](/docs/ai-gateway/features/multimodal) for supported formats and full examples. ## AI Gateway features All **AI Gateway** features apply to the Responses API. Most are configured per request through request-body fields; budgets are configured in the console and apply by scope. | Feature | How it applies | Request field | Docs | | ---------------- | ---------------------------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------- | | Fallbacks | Try a fallback model when the primary fails | `fallbacks` | [Retries](/docs/ai-gateway/features/retries) | | Retries | Automatic retries on configured status codes | `retry` | [Retries](/docs/ai-gateway/features/retries) | | Response caching | Exact-match cache with TTL control | `cache`, `cache_control` | [Cache](/docs/ai-gateway/features/cache) | | Guardrails | LLM and Python evaluators on requests and responses | `guardrails` | [Guardrails](/docs/ai-gateway/configuration/guardrails) | | Budgets | Spending limits by workspace, project, identity, API key, provider, or model | — | [Budgets](/docs/ai-gateway/budgets) | | Load balancing | Distribute requests across models | `load_balancer` | [Load Balancing](/docs/ai-gateway/features/load-balancing) | | Plugins | PII redaction transforms on request and response text | `plugins` | [PII Redaction](/docs/ai-gateway/features/plugins/pii-redaction) | | Trace masking | Mask request and response data in stored traces | `security` | [Security](/docs/ai-gateway/features/security) | | Threads | Group related requests in observability | `thread` | [Thread Management](/docs/ai-gateway/thread-management) | ## See also * [Create Response](/reference/responses/create-response): full request and response reference * [Retrieve Response](/reference/responses/retrieve-response): fetch a stored response by ID * [OpenAI-Compatible API](/docs/ai-gateway/features/openai-compatible-api): base URL and all supported endpoints * [Run Agents](/docs/ai-studio/ai-engineering/run-agents): invoke a configured agent through the Responses API * [Reasoning](/docs/ai-gateway/features/reasoning): `reasoning` effort for OpenAI models via the Responses API * [Memory stores](/docs/ai-studio/ai-engineering/memory-stores): persistent memory across requests with the `memory` field # Fallbacks and retries in the AI Gateway Source: https://docs.orq.ai/docs/ai-gateway/features/retries Retry failed LLM requests with exponential backoff and configure fallback models in Orq.ai to handle rate limits, server errors, and network failures. **Use Cases** * Surviving transient provider errors without surfacing failures to end users. * Automatic failover to a backup provider when the primary is degraded or rate-limited. * Absorbing short rate-limit bursts without manual intervention or custom retry logic. * Meeting availability SLAs on production features without adding retry code to every service. *** Retry failed requests automatically with exponential backoff. Configure which HTTP error codes trigger retries and how many attempts to make. Route to a different model when the primary fails. Define a fallback chain across providers for high availability. ## Retries Automatically retry failed requests with exponential backoff. ### Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Analyze customer feedback", "retry": {"count": 3, "on_codes": [429, 500, 502, 503, 504]} }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Analyze customer feedback", retry: { count: 3, on_codes: [429, 500, 502, 503, 504], }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Analyze customer feedback", extra_body={ "retry": {"count": 3, "on_codes": [429, 500, 502, 503, 504]} }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Analyze customer feedback" }], retry: { count: 3, on_codes: [429, 500, 502, 503, 504], }, }); ``` ### Configuration | Parameter | Type | Required | Description | | ---------- | --------- | -------- | -------------------------------------------------------- | | `count` | number | Yes | Max retry attempts (1-5) | | `on_codes` | number\[] | No | HTTP status codes that trigger retries (default: \[429]) | ### Error Codes | Code | Meaning | Retry? | Common Cause | | ----- | --------------------- | ------------------------- | ------------------------------------- | | `429` | Rate limit exceeded | Yes | Too many requests | | `500` | Internal server error | Yes | Provider issue | | `501` | Not implemented | No | Definitive; retrying will not succeed | | `502` | Bad gateway | Yes | Network/Gateway issue | | `503` | Service unavailable | Yes | Provider maintenance | | `504` | Gateway timeout | Yes | Provider overload | | `400` | Bad request | No | Invalid parameters | | `401` | Unauthorized | No | Invalid API key | | `403` | Forbidden | No | Access denied | ### Retry Strategies ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Conservative retry: { count: 2, on_codes: [429, 503] // Only rate limits and service unavailable } // Balanced (recommended) retry: { count: 3, on_codes: [429, 500, 502, 503, 504] // All transient errors } // Aggressive retry: { count: 5, on_codes: [429, 500, 502, 503, 504] // Max retries } ``` ### Backoff Algorithm #### Exponential backoff with jitter * Attempt 1: 1s (±25%). * Attempt 2: 2s (±25%). * Attempt 3: 4s (±25%). * Attempt 4: 8s (±25%). * Attempt 5: 16s (±25%). **Maximum total delay**: \~31 seconds for 5 retries ### Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Analyze customer feedback and provide sentiment analysis", "retry": { "count": 3, "on_codes": [429, 500, 502, 503, 504] } }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Analyze customer feedback and provide sentiment analysis" } ], "retry": { "count": 3, "on_codes": [429, 500, 502, 503, 504] } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Analyze customer feedback and provide sentiment analysis", retry: { count: 3, on_codes: [429, 500, 502, 503, 504], }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Analyze customer feedback and provide sentiment analysis", extra_body={ "retry": { "count": 3, "on_codes": [429, 500, 502, 503, 504], } }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [ { role: "user", content: "Analyze customer feedback and provide sentiment analysis", }, ], retry: { count: 3, on_codes: [429, 500, 502, 503, 504], }, }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Analyze customer feedback and provide sentiment analysis", } ], extra_body={ "retry": { "count": 3, "on_codes": [429, 500, 502, 503, 504], } }, ) ``` ### Best Practices #### Production recommendations Follow this advice for a solid production setup: * Use `count: 2-3` for balance of reliability and speed. * Always include `429` (rate limits) in `on_codes`. * Monitor retry rates to detect systemic issues. * Implement a circuit breaker for persistent failures. #### Error handling ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); try { const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Hello", }); } catch (error) { if (error instanceof OpenAI.APIError) { if (error.status === 400) { console.error('Bad request:', error.message); } else if (error.status >= 500) { console.error('Server error:', error.message); } } } ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); try { const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Hello" }], }); } catch (error) { if (error instanceof OpenAI.APIError) { if (error.status === 400) { // Don't retry client errors - fix the request console.error('Bad request:', error.message); } else if (error.status >= 500) { // Server errors might need manual intervention console.error('Server error:', error.message); } } } ``` ### Troubleshooting **High retry rates** * Check whether rate limits are being hit frequently. * Verify API keys have sufficient quotas. * Monitor provider status pages for outages. **Slow response times** * Reduce retry count for latency-sensitive apps. * Use shorter timeout values with retries. * Consider fallbacks for faster alternatives. **Still getting errors** * Check if error codes are in `on_codes` list. * Verify retry count isn't exhausted. * Review provider-specific error documentation. ### Monitoring Track these retry metrics: ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const retryMetrics = { totalRequests: 0, retriedRequests: 0, retriesByAttempt: { 1: 0, 2: 0, 3: 0 }, // Retry attempt distribution retriesByCode: { 429: 0, 500: 0 }, // By error code avgRetryLatency: 0, // Added latency from retries finalFailures: 0, // Requests that failed after all retries }; ``` ### Limitations * **Increased latency**: Retries add delay (up to 31s for 5 attempts). * **Cost implications**: Failed requests may still incur charges. * **Rate limit consumption**: Each retry counts against quotas. * **Limited retries**: Maximum 5 attempts to prevent excessive delays. * **Non-retryable errors**: 4xx client errors are not retried. ### Advanced Usage **Environment-specific configs:** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const retryConfig = { development: { count: 1, on_codes: [429] }, // Fast feedback staging: { count: 2, on_codes: [429, 503] }, // Light retries production: { count: 3, on_codes: [429, 500, 502, 503, 504] }, // Full protection }; ``` **With other features:** ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "retry": { "count": 3, "on_codes": [429, 503] }, "timeout": { "call_timeout": 10000 }, "fallbacks": [{ "model": "backup-model" }], "cache": { "type": "exact_match", "ttl": 300 } } ``` **Custom retry logic (client-side):** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const customRetry = async (requestFn, maxAttempts = 3) => { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await requestFn(); } catch (error) { if (attempt === maxAttempts || error.status < 500) { throw error; // Final attempt or non-retryable error } await new Promise( (resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1000), // Exponential backoff ); } } }; ``` ## Fallbacks Automatically switch to a different model when the primary fails. ### Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [{ "role": "user", "content": "Generate a product description" }], "fallbacks": [{ "model": "openai/gpt-5.6-sol" }, { "model": "azure/gpt-5.6-sol" }] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Generate a product description", fallbacks: [{ model: "openai/gpt-5.6-sol" }, { model: "azure/gpt-5.6-sol" }], }); console.log(response.output_text); ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Generate a product description" }], fallbacks: [{ model: "openai/gpt-5.6-sol" }, { model: "azure/gpt-5.6-sol" }], }); ``` ### Configuration | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ---------------------------------------------- | | `fallbacks` | Array | Yes | List of fallback models in order of preference | | `model` | string | Yes | Model identifier for each fallback | ### Trigger Conditions Fallbacks activate on these errors: | Error Code | Description | Triggers Fallback | | ---------- | --------------------- | ------------------------- | | `429` | Rate limit exceeded | Yes | | `500` | Internal server error | Yes | | `501` | Not implemented | No | | `502` | Bad gateway | Yes | | `503` | Service unavailable | Yes | | `504` | Gateway timeout | Yes | | `400` | Bad request | No | | `401` | Unauthorized | No | | `403` | Forbidden | No | ### Best Practices Use a maximum of 3 fallback models. Order them by preference or cost, and choose models with similar capabilities. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Cost-optimized: cheap then expensive fallbacks: [{ model: "openai/gpt-5.4-mini" }, { model: "openai/gpt-5.6-sol" }]; // Reliability-optimized: different providers fallbacks: [ { model: "openai/gpt-5.6-sol" }, { model: "anthropic/claude-sonnet-5" }, { model: "azure/gpt-5.6-sol" }, ]; ``` ### Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Generate a product description", "fallbacks": [ { "model": "openai/gpt-5.6-sol" }, { "model": "azure/gpt-5.6-sol" } ] }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [{ "role": "user", "content": "Generate a product description" }], "fallbacks": [ { "model": "openai/gpt-5.6-sol" }, { "model": "azure/gpt-5.6-sol" } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Generate a product description", fallbacks: [{ model: "openai/gpt-5.6-sol" }, { model: "azure/gpt-5.6-sol" }], }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Generate a product description", extra_body={ "fallbacks": [ {"model": "openai/gpt-5.6-sol"}, {"model": "azure/gpt-5.6-sol"} ] } ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Generate a product description" }], fallbacks: [{ model: "openai/gpt-5.6-sol" }, { model: "azure/gpt-5.6-sol" }], }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[{"role": "user", "content": "Generate a product description"}], extra_body={ "fallbacks": [ {"model": "openai/gpt-5.6-sol"}, {"model": "azure/gpt-5.6-sol"} ] } ) ``` ### Limitations * **Response consistency**: Different models may return varying output styles. * **Parameter support**: Not all providers support identical parameters. * **Cost implications**: Failed requests may still incur charges from the primary provider. * **Latency impact**: Sequential attempts add processing time. * **Provider dependencies**: Requires API keys for all fallback providers. # Mask sensitive content in traces Source: https://docs.orq.ai/docs/ai-gateway/features/security Control which request and response content gets written to stored traces using the security parameter on the AI Gateway. **Use Cases** * Keeping system prompts out of stored traces while still tracing the request. * Excluding user-submitted content from logs to meet data-handling requirements. * Redacting model output from traces without changing what the caller receives. The `security` parameter controls what gets written to stored traces. It does not change the live request or response: the caller always receives the full, unmasked output. Only the copy persisted to trace storage is affected. Masking `input`, `output`, `system`, or `all` with `security.mask` only affects the copy of the request written to stored traces. **The full, unmasked payload is still sent to the model provider.** To prevent PII from leaving in the live request or response, enable the [**PII Redaction**](/docs/ai-gateway/features/plugins/pii-redaction) plugin. `security` is set per request. There is no workspace, project, or API-key default. A request sent without a `security` block is traced in full. To mask consistently, include `security.mask` on every request, for example by setting it as a default on your SDK client so it is attached automatically. ## Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Summarize AI trends for 2025", "security": { "mask": ["input"] } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Summarize AI trends for 2025", // @ts-ignore - orq.ai extension security: { mask: ["input"] }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Summarize AI trends for 2025", extra_body={"security": {"mask": ["input"]}}, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Summarize AI trends for 2025" }], // @ts-ignore - orq.ai extension security: { mask: ["input"] }, }); ``` ## Configuration | Parameter | Type | Required | Description | | --------- | --------- | -------- | ------------------------------------------------------------------------------------------------------- | | `mask` | string\[] | No | Which content to mask in stored traces: `input`, `output`, `system`, `metadata`, `variables`, or `all`. | An unrecognized value in `mask` is rejected with a 400 error. ## What each value masks | Value | Effect on stored traces | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `input` | Blanks user message content, and (on tool spans) the tool-call arguments. | | `system` | Blanks system and developer message content, and (on tool spans) the resolved tool variables. Also strips `description` and the parameter schema (`parameters` or `input_schema`) from tool definitions, keeping only name and type. | | `output` | Blanks assistant message content, and (on tool spans) the tool-call result. | | `metadata` | Removes custom request metadata (`metadata.*` and `orq.metadata` attributes) from stored traces. | | `variables` | Removes template and tool variables (`orq.variables.*` attributes) from stored traces. | | `all` | Shorthand for `input`, `system`, `output`, `metadata`, and `variables` together. | ## Coverage `security` is supported across the AI Gateway's endpoints, including `responses`, `chat/completions`, `completions`, `embeddings`, `images`, `ocr`, `rerank`, `speech`, `transcriptions`, and `translations`. `security` is unrelated to the [**PII Redaction**](/docs/ai-gateway/features/plugins/pii-redaction) plugin, which rewrites sensitive values in the live request and response, and to [**Guardrails**](/docs/ai-gateway/configuration/guardrail-rules), which can block a request outright. `security` only changes what is written to trace storage. # Server tools Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools Let models search the web, run code, query knowledge bases, consult other models, and complete other tasks through tools operated by the AI Gateway. Server tools let a model take actions during a request without a separate tool executor in the application. Add a supported tool to the standard `tools` array. The **AI Gateway** presents it to the model, runs each tool call, and returns the model's final answer. Server tools are available on: * [`POST /v3/router/responses`](/reference/responses/create-response) * [`POST /v3/router/chat/completions`](/reference/chat/create-chat-completion) The selected model must support tool calling. ## Server tools and function tools | | Server tool | Function tool | | ---------------------- | -------------------------------------------------- | ------------------------------------------- | | Who decides to call it | The model | The model | | Who executes it | **Orq.ai** | The application | | Request type | `orq:*` for most tools | `function` | | Calls per request | Zero or more, within the configured limits | Zero or more | | Result handling | The **AI Gateway** returns the result to the model | The application submits the function result | Server tools can share a request with function tools. Provider-hosted tools such as `web_search` or `file_search` are separate from the `orq:*` tools documented here. ## How server tools work 1. Add one or more server tools to the request's `tools` array. 2. The model chooses whether to call a tool and supplies its runtime arguments. 3. The **AI Gateway** executes the tool and returns the result to the model. 4. The model can call another tool or finish the response. `max_tool_calls` limits the server-side loop. Chat Completions defaults to 10 calls and accepts values from 1 to 30. The field is optional on the Responses API. The model sees each server tool under its type with `orq:` replaced by `orq_` (for example `orq:web_search` becomes `orq_web_search`), and output items carry that name. Tool names must be unique within a request, so a function tool may not reuse a server tool's name. ## Quick start This example gives the model access to web search and the current date. The model can use either tool, both tools, or neither. The upstream OpenAI TypeScript types do not define `orq:*` tools, so the TypeScript examples cast the `tools` array. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "What changed in EU AI Act enforcement this month?" } ], "tools": [ { "type": "orq:web_search", "max_results": 5 }, { "type": "orq:datetime", "timezone": "Europe/Amsterdam" } ], "max_tool_calls": 10 }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: 'https://my.orq.ai/v3/router', }); const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'What changed in EU AI Act enforcement this month?', }, ], tools: [ { type: 'orq:web_search', max_results: 5 }, { type: 'orq:datetime', timezone: 'Europe/Amsterdam' }, ] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "What changed in EU AI Act enforcement this month?", } ], tools=[ {"type": "orq:web_search", "max_results": 5}, {"type": "orq:datetime", "timezone": "Europe/Amsterdam"}, ], ) print(response.choices[0].message.content) ``` ## Available tools `orq:web_search`: Search the public web for current information. `orq:web_fetch`: Fetch and extract text from a public URL. `orq:datetime`: Return the current date and time in an IANA timezone. `orq:image_generation`: Generate an image with a configured image model. `orq:code_interpreter`: Run Python in an isolated sandbox. `orq:shell`: Run commands in an isolated Linux sandbox. `orq:apply_patch`: Validate file changes before the application applies them. `retrieve_knowledge_bases` and `query_knowledge_base`: List and query knowledge bases in the workspace. `orq:search_models`: Search the **Orq.ai** model catalog by capability and cost. `orq:subagent`: Delegate a self-contained task to another model. `orq:advisor`: Ask another model for advice during a response. `orq:fusion`: Compare answers from a panel of models. ## Combine server tools with functions Function tools keep the standard OpenAI shape. The **AI Gateway** executes `orq:*` tools and returns function calls to the application. ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Find the latest EUR/USD rate and save it." } ], "tools": [ { "type": "orq:web_search", "max_results": 3 }, { "type": "function", "function": { "name": "save_exchange_rate", "description": "Save an exchange rate in the application database", "parameters": { "type": "object", "properties": { "pair": { "type": "string" }, "rate": { "type": "number" } }, "required": ["pair", "rate"] } } } ] } ``` See [Tool calling and function execution](/docs/ai-gateway/features/tool-calling) for the function-call response loop. ## Usage reporting When a counted server tool runs, the response includes its call count in `usage.server_tool_use`. ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "usage": { "input_tokens": 1200, "output_tokens": 300, "server_tool_use": { "web_search_requests": 2, "web_fetch_requests": 1, "subagent_requests": 1 }, "server_tool_use_details": { "tool_calls_executed": 4, "tool_calls_requested": 4 } } } ``` The usage object can include these counters: | Field | Tool | | --------------------------- | ---------------- | | `web_search_requests` | Web search | | `web_fetch_requests` | Web fetch | | `datetime_requests` | Datetime | | `code_interpreter_sessions` | Code interpreter | | `advisor_requests` | Advisor | | `subagent_requests` | Subagent | | `search_models_requests` | Search models | | `image_generation_calls` | Image generation | | `fusion_requests` | Fusion | | `shell_commands` | Shell | Knowledge-base retrieval and apply-patch calls do not add a field to `usage.server_tool_use`. `usage.server_tool_use_details` totals the same calls across tools: `tool_calls_requested` counts every server tool call the model made and `tool_calls_executed` counts the ones the gateway ran (a call rejected by a `max_uses` limit is requested but not executed). Chat Completions returns these counters but not the tool result itself; read the result from the Responses API output items, or send `store: true` and retrieve the response. ## Chat Completions limitations On `POST /v3/router/chat/completions`, a request that includes an `orq:*` server tool cannot also include: * `n` greater than `1` * provider-hosted tool types such as `web_search` or `file_search` The endpoint returns `400` for these combinations. Function tools remain supported alongside server tools. `plugins`, `guardrails`, and `evaluators` are applied the same way as on requests without server tools: caller-supplied config, matched routing and guardrail rules, and workspace-enforced defaults (such as the PII redaction floor) all take effect. In streaming mode, output guardrails are skipped and output evaluators still run once the response has been assembled, matching the non-server-tool streaming behavior. Chat Completions is stateless unless `store` is set to `true`. ## Legacy names The following aliases remain accepted: | Current type | Legacy alias | | ---------------- | ------------------- | | `orq:web_search` | `orq:google_search` | | `orq:web_fetch` | `orq:web_scraper` | | `orq:datetime` | `orq:current_date` | | `orq:subagent` | `orq:sidekick` | # Advisor server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/advisor Let a model consult a configured secondary model for advice during a response. The `orq:advisor` tool lets the primary model ask another model for advice during a response. The advisor receives the conversation transcript, the model's question, and optional context. Its answer goes back to the primary model, which writes the final response. Advisor is useful at a decision point or before the primary model commits to a high-cost action. It is not a separate user-facing answer. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Review this migration plan and identify the highest-risk assumption." } ], "tools": [ { "type": "orq:advisor", "model": "anthropic/claude-sonnet-5", "max_transcript_tokens": 8000, "max_uses": 1 } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Review this migration plan and identify the highest-risk assumption.', }, ], tools: [ { type: 'orq:advisor', model: 'anthropic/claude-sonnet-5', max_transcript_tokens: 8000, max_uses: 1, }, ] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Review this migration plan and identify the highest-risk assumption.", } ], tools=[ { "type": "orq:advisor", "model": "anthropic/claude-sonnet-5", "max_transcript_tokens": 8000, "max_uses": 1, } ], ) print(response.choices[0].message.content) ``` ## Configuration | Parameter | Type | Required | Default | Description | | ----------------------- | ------- | -------- | ---------------- | --------------------------------------------------------------------------------------------------------- | | `type` | string | Yes | | Must be `orq:advisor`. | | `model` | string | Yes | | Advisor model in `provider/model` format. | | `max_tokens` | integer | No | Provider default | Maximum advisor output tokens. Accepted range: 0 to 128,000. `0` uses the provider default. | | `max_transcript_tokens` | integer | No | Full transcript | Approximate transcript budget. The newest user turn is always retained. `0` includes the full transcript. | | `reasoning_effort` | string | No | Provider default | `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`, when supported by the advisor model. | | `temperature` | number | No | Provider default | Advisor sampling temperature from 0 to 2. The selected model may impose a lower maximum. | | `max_uses` | integer | No | Unlimited | Maximum consultations during the request. Set `0` or omit the field for no tool-specific limit. | Transcript size is estimated from the serialized conversation. When a limit is set, the **AI Gateway** keeps the newest complete user turns that fit. ## Cost and usage Advisor tokens are billed at the selected model's standard rate. Each Advisor call appears at `usage.server_tool_use.advisor_requests`. # Apply patch server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/apply-patch Let a model propose validated file changes while the application keeps control of filesystem writes. The `orq:apply_patch` tool lets a model propose file changes. **Orq.ai** validates the operation, path, and diff. The application receives each valid patch as a pending function call and decides whether to apply it. This is a human-in-the-loop tool. **Orq.ai** never writes to the application filesystem. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). The Responses API is used here because it exposes the pending call and follow-up output directly. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Create hello.txt containing the line Hello from Orq.ai", "tools": [ { "type": "orq:apply_patch" } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.responses.create({ model: 'openai/gpt-5.4-mini', input: 'Create hello.txt containing the line Hello from Orq.ai', tools: [{ type: 'orq:apply_patch' }] as any, }); console.log(response.output); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.responses.create( model="openai/gpt-5.4-mini", input="Create hello.txt containing the line Hello from Orq.ai", tools=[{"type": "orq:apply_patch"}], ) print(response.output) ``` ## Patch operations The model supplies three arguments in the pending call: | Argument | Type | Description | | ----------- | ------ | ------------------------------------------------------ | | `operation` | string | `create_file`, `update_file`, or `delete_file`. | | `path` | string | File path. Paths containing `..` are rejected. | | `diff` | string | V4A-style diff. Use an empty string for `delete_file`. | Validation depends on the operation: * `create_file`: every non-empty diff line must start with `+`. * `update_file`: lines may start with a space, `+`, `-`, or `@@`, and the diff must contain an addition or deletion. * `delete_file`: the diff must be empty. Malformed patches are returned to the model for correction inside the tool loop. Only a valid patch reaches the application. ## Apply the patch Read the pending `orq_apply_patch` function call, review and apply it in the application, then submit a standard function-call output. See [Tool calling and function execution](/docs/ai-gateway/features/tool-calling) for the response loop. Apply patch has no additional charge beyond model tokens. Calls are not included in `usage.server_tool_use`. # Code interpreter server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/code-interpreter Run Python in an isolated sandbox during a model response with the orq:code_interpreter server tool. The `orq:code_interpreter` tool runs Python in an isolated, workspace-scoped sandbox. It is suited to calculations, data transformation, parsing, and other tasks where executing code is more reliable than reasoning in text. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Calculate the compound annual growth rate from 4.2 million to 7.1 million over five years." } ], "tools": [ { "type": "orq:code_interpreter" } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Calculate the compound annual growth rate from 4.2 million to 7.1 million over five years.', }, ], tools: [{ type: 'orq:code_interpreter' }] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Calculate the compound annual growth rate from 4.2 million to 7.1 million over five years.", } ], tools=[{"type": "orq:code_interpreter"}], ) print(response.choices[0].message.content) ``` The model writes the Python code. A value assigned to `result` and anything written to standard output are returned to the model. ## Configuration | Parameter | Type | Required | Default | Description | | ------------------- | --------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------ | | `type` | string | Yes | | Must be `orq:code_interpreter`. | | `files` | object\[] | No | None | Workspace files to stage in `/workspace`. Maximum: 10 files. | | `files[].file_id` | string | Yes | | Workspace file ID. | | `files[].name` | string | Yes | | File name exposed in `/workspace`. Names must be unique and may contain letters, numbers, `.`, `_`, and `-`. | | `network.mode` | string | No | `disabled` | Network policy intent. Accepted values are `disabled` and `allowlist`. | | `network.allowlist` | string\[] | No | None | Bare hostnames or IPv4 addresses. Required when mode is `allowlist`. Maximum: 50 entries. | Each staged file can be up to 10 MB. Network settings are accepted and validated, but sandbox egress enforcement is still rolling out. Sandbox executions retain the deployment's default public internet access until enforcement is enabled. ## Availability and usage The deployment must have sandbox infrastructure configured. When it is unavailable, the model receives a tool error instead of a Python result. Code interpreter has no separate server-tool charge in this release. The number of sessions appears at `usage.server_tool_use.code_interpreter_sessions`. # Datetime server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/datetime Give a model the current date and time in a chosen IANA timezone with the orq:datetime server tool. The `orq:datetime` tool gives a model the current date and time. Set a default IANA timezone on the tool entry, or omit it to use UTC. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Is the Amsterdam support desk open now?" } ], "tools": [ { "type": "orq:datetime", "timezone": "Europe/Amsterdam" } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Is the Amsterdam support desk open now?' }, ], tools: [{ type: 'orq:datetime', timezone: 'Europe/Amsterdam' }] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ {"role": "user", "content": "Is the Amsterdam support desk open now?"} ], tools=[ {"type": "orq:datetime", "timezone": "Europe/Amsterdam"} ], ) print(response.choices[0].message.content) ``` ## Configuration | Parameter | Type | Required | Default | Description | | ---------- | ------ | -------- | ------- | ------------------------------------------------------------------------ | | `type` | string | Yes | | Must be `orq:datetime`. | | `timezone` | string | No | `UTC` | Default IANA timezone, such as `Europe/Amsterdam` or `America/New_York`. | The model can request an ISO timestamp or a human-readable date during the tool call. Invalid timezone identifiers are returned to the model as tool errors. ## Cost and usage Datetime calls have no additional charge. The number of calls appears at `usage.server_tool_use.datetime_requests`. # Fusion server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/fusion Run a prompt across a model panel and return a structured comparison to the primary model. The `orq:fusion` tool sends one prompt to a panel of models in parallel. An analyst model compares the successful responses and returns consensus, contradictions, unique insights, and blind spots to the primary model. Panel members do not receive the parent conversation or tools. The primary model must give Fusion a self-contained prompt. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Compare the main architectural options for a multi-region event ingestion service." } ], "tools": [ { "type": "orq:fusion", "analysis_models": [ "openai/gpt-5.4", "anthropic/claude-sonnet-5" ], "model": "openai/gpt-5.4", "max_uses": 1 } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Compare the main architectural options for a multi-region event ingestion service.', }, ], tools: [ { type: 'orq:fusion', analysis_models: ['openai/gpt-5.4', 'anthropic/claude-sonnet-5'], model: 'openai/gpt-5.4', max_uses: 1, }, ] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Compare the main architectural options for a multi-region event ingestion service.", } ], tools=[ { "type": "orq:fusion", "analysis_models": [ "openai/gpt-5.4", "anthropic/claude-sonnet-5", ], "model": "openai/gpt-5.4", "max_uses": 1, } ], ) print(response.choices[0].message.content) ``` ## Configuration | Parameter | Type | Required | Default | Description | | ------------------ | --------- | -------- | ----------------- | ------------------------------------------------------------------------------------------------------- | | `type` | string | Yes | | Must be `orq:fusion`. | | `analysis_models` | string\[] | Yes | | Panel models in `provider/model` format. Include 1 to 8 models. | | `model` | string | No | First panel model | Analyst model that compares the panel responses. | | `max_tokens` | integer | No | Provider default | Maximum output tokens for each inner call. Accepted range: 0 to 128,000. `0` uses the provider default. | | `reasoning_effort` | string | No | Provider default | `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`, when supported by the panel models. | | `temperature` | number | No | Provider default | Panel sampling temperature from 0 to 2. The analyst always uses `0`. | | `max_uses` | integer | No | `1` | Maximum Fusion calls during the request. A missing or non-positive value uses `1`. | ## Failure behavior If some panel calls fail, Fusion compares the successful responses and includes the failed model details in its result. If the analyst fails, the panel responses are returned with an `analysis_error`. If every panel call fails, the tool returns `all_panels_failed` to the primary model. ## Cost and usage Each panel response and the analyst response are billed at their selected models' standard token rates. Each Fusion invocation appears at `usage.server_tool_use.fusion_requests`. # Image generation server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/image-generation Let a model generate an image during a response with a configured image model. The `orq:image_generation` tool lets a text model create an image during a response. The tool entry selects the image model and rendering options. The calling model supplies only the image prompt. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Create a square product illustration of a green desk lamp." } ], "tools": [ { "type": "orq:image_generation", "model": "openai/gpt-image-2", "size": "1024x1024", "quality": "high" } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Create a square product illustration of a green desk lamp.', }, ], tools: [ { type: 'orq:image_generation', model: 'openai/gpt-image-2', size: '1024x1024', quality: 'high', }, ] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Create a square product illustration of a green desk lamp.", } ], tools=[ { "type": "orq:image_generation", "model": "openai/gpt-image-2", "size": "1024x1024", "quality": "high", } ], ) print(response.choices[0].message.content) ``` The tool returns an image URL to the calling model. **Orq.ai** stores the generated image in workspace storage when uploads are available, otherwise it uses a provider-hosted URL. ## Configuration | Parameter | Type | Required | Description | | -------------------- | ------- | -------- | --------------------------------------------------- | | `type` | string | Yes | Must be `orq:image_generation`. | | `model` | string | Yes | Image model in `provider/model` format. | | `size` | string | No | Image dimensions supported by the selected model. | | `quality` | string | No | Image quality supported by the selected model. | | `background` | string | No | Background option supported by the selected model. | | `output_format` | string | No | Output format supported by the selected model. | | `output_compression` | integer | No | Compression level from 0 to 100, when supported. | | `moderation` | string | No | Moderation setting supported by the selected model. | | `style` | string | No | Image style supported by the selected model. | Rendering options are model-dependent. Unsupported values return an error from the selected image provider. ## Cost and usage The image model call is billed at the selected model's standard rate. Each tool call appears at `usage.server_tool_use.image_generation_calls`. # Knowledge-base server tools Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/knowledge-bases List and query knowledge bases from the Responses API so a model can answer from workspace documents. Two server tools give a model access to knowledge bases in the current workspace: * `retrieve_knowledge_bases` lists available knowledge bases and their keys. * `query_knowledge_base` retrieves document content from one knowledge base. Knowledge-base server tools are currently supported on the Responses API. Use both tools when the knowledge-base key is not already known. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "What is the refund policy in the support knowledge base?", "tools": [ { "type": "retrieve_knowledge_bases" }, { "type": "query_knowledge_base" } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.responses.create({ model: 'openai/gpt-5.4-mini', input: 'What is the refund policy in the support knowledge base?', tools: [ { type: 'retrieve_knowledge_bases' }, { type: 'query_knowledge_base' }, ] as any, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.responses.create( model="openai/gpt-5.4-mini", input="What is the refund policy in the support knowledge base?", tools=[ {"type": "retrieve_knowledge_bases"}, {"type": "query_knowledge_base"}, ], ) print(response.output_text) ``` ## How retrieval works 1. `retrieve_knowledge_bases` returns knowledge-base keys and, when requested by the model, their descriptions. 2. The model selects a key and calls `query_knowledge_base` with a search query. 3. The query tool returns matching document content to the model. The list tool returns metadata, not document content. A model must call the query tool before it can answer from a knowledge base. ## Model-supplied arguments The tool entries have no configuration fields beyond `type`. The model supplies these arguments when it calls them: | Tool | Argument | Type | Description | | -------------------------- | ---------------------- | ------- | ----------------------------------------------- | | `retrieve_knowledge_bases` | `include_descriptions` | boolean | Include the description of each knowledge base. | | `query_knowledge_base` | `query` | string | Search query. | | `query_knowledge_base` | `knowledge_base_key` | string | Key returned by `retrieve_knowledge_bases`. | | `query_knowledge_base` | `include_metadata` | boolean | Include document metadata with the results. | Knowledge-base calls are not included in `usage.server_tool_use`. # Search models server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/search-models Let a model search the Orq.ai catalog by provider, context length, capability, or input cost. The `orq:search_models` tool searches the **Orq.ai** model catalog during a response. It is useful when a model needs to choose another model based on context length, tool support, vision support, or input cost. Results come from the public model registry plus the private models enabled in the current workspace; public results are not filtered to models enabled in the workspace. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Find a model with vision and tool support for a document workflow." } ], "tools": [ { "type": "orq:search_models", "max_results": 5 } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Find a model with vision and tool support for a document workflow.', }, ], tools: [{ type: 'orq:search_models', max_results: 5 }] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Find a model with vision and tool support for a document workflow.", } ], tools=[ {"type": "orq:search_models", "max_results": 5} ], ) print(response.choices[0].message.content) ``` ## Configuration | Parameter | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ------------------------------------------------- | | `type` | string | Yes | | Must be `orq:search_models`. | | `max_results` | integer | No | `5` | Maximum models returned. Accepted range: 1 to 20. | ## Model-supplied filters The model can combine these filters in a tool call: | Filter | Description | | -------------------- | ------------------------------------------------- | | `query` | Match model ID, display name, or model family. | | `provider` | Match one provider. | | `min_context_length` | Require at least this many context tokens. | | `require_tools` | Return only models that support function calling. | | `require_vision` | Return only models that support image input. | | `max_input_cost` | Maximum input cost in USD per 1,000 tokens. | Each result includes an `id` field (`provider/model`, e.g. `openai/gpt-5.4-mini`) — this is the canonical routable identifier to pass as `model` in subsequent requests, since the bare model name is not unique across providers. Private workspace models carry the `workspaceKey@` prefix (e.g. `my-workspace@openai/my-finetune`). Results are sorted by `id` and include model costs, context length, and capability flags. ## Cost and usage Catalog searches have no additional charge. The number of searches appears at `usage.server_tool_use.search_models_requests`. Pair this tool with [Subagent](/docs/ai-gateway/features/server-tools/subagent) when the calling model should select a worker model before delegating a task. # Shell server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/shell Run shell commands in an isolated Linux sandbox during a model response with the orq:shell server tool. The `orq:shell` tool runs commands in an isolated Linux sandbox. One sandbox is created lazily for the response and reused for later shell calls in the same response, so files and the working directory carry across commands. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Create a CSV with the first ten prime numbers, then verify the file." } ], "tools": [ { "type": "orq:shell", "max_uses": 5, "timeout_seconds": 30 } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Create a CSV with the first ten prime numbers, then verify the file.', }, ], tools: [{ type: 'orq:shell', max_uses: 5, timeout_seconds: 30 }] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Create a CSV with the first ten prime numbers, then verify the file.", } ], tools=[ {"type": "orq:shell", "max_uses": 5, "timeout_seconds": 30} ], ) print(response.choices[0].message.content) ``` The model supplies the command. It runs through `/bin/sh` in a Debian Linux sandbox with Python 3.13 and the standard coreutils, so pipes, redirects, `&&` chains, and multi-line scripts behave as they would in a terminal. The tool returns separate `stdout` and `stderr` values plus the exit code. Output that exceeds the configured length ends with `[output truncated]`. ## Configuration | Parameter | Type | Required | Default | Description | | ------------------- | ------- | -------- | --------- | ------------------------------------------------------------------------------------------ | | `type` | string | Yes | | Must be `orq:shell`. | | `max_uses` | integer | No | Unlimited | Maximum commands during the request. Set `0` or omit the field for no tool-specific limit. | | `timeout_seconds` | integer | No | `60` | Per-command timeout. Accepted range: 1 to 300 seconds. | | `max_output_length` | integer | No | `16000` | Maximum characters returned for standard output and standard error. Minimum: 1. | The sandbox lasts only for the current response. Cross-request containers are not supported. ## Availability and usage The deployment must have a sandbox provider configured. This can be a cloud or on-premise sandbox provider that implements the **Orq.ai** sandbox interface. Shell has no separate server-tool charge in this release. Each attempted command appears at `usage.server_tool_use.shell_commands`, including calls blocked by `max_uses`. # Subagent server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/subagent Delegate a self-contained task to a configured worker model during a response. The `orq:subagent` tool delegates a self-contained task to another model. The worker receives the task, optional context, and the configuration on the tool entry. It does not receive the parent conversation. Use a smaller or faster worker for extraction, summarization, drafting, or data transformation while the primary model continues to own the final response. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "messages": [ { "role": "user", "content": "Extract the dates and owners from this project update, then summarize the risks." } ], "tools": [ { "type": "orq:subagent", "model": "openai/gpt-5.4-mini", "output_format": "JSON with dates, owners, and risks", "max_uses": 2 } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4', messages: [ { role: 'user', content: 'Extract the dates and owners from this project update, then summarize the risks.', }, ], tools: [ { type: 'orq:subagent', model: 'openai/gpt-5.4-mini', output_format: 'JSON with dates, owners, and risks', max_uses: 2, }, ] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4", messages=[ { "role": "user", "content": "Extract the dates and owners from this project update, then summarize the risks.", } ], tools=[ { "type": "orq:subagent", "model": "openai/gpt-5.4-mini", "output_format": "JSON with dates, owners, and risks", "max_uses": 2, } ], ) print(response.choices[0].message.content) ``` The primary model supplies a `task` and optional `context` when it calls the tool. The worker's completed output is returned to the primary model. ## Configuration | Parameter | Type | Required | Default | Description | | ------------------ | ------- | -------- | ---------------------- | -------------------------------------------------------------------------------------------------- | | `type` | string | Yes | | Must be `orq:subagent`. | | `model` | string | Yes | | Worker model in `provider/model` format. | | `max_tokens` | integer | No | Provider default | Maximum worker output tokens. Accepted range: 0 to 128,000. `0` uses the provider default. | | `reasoning_effort` | string | No | Provider default | `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`, when supported by the worker model. | | `temperature` | number | No | Provider default | Worker sampling temperature from 0 to 2. The selected model may impose a lower maximum. | | `system_prompt` | string | No | Built-in worker prompt | Replace the worker's system prompt. | | `output_format` | string | No | None | Add output-format guidance to the worker task. | | `max_uses` | integer | No | Unlimited | Maximum delegations during the request. Set `0` or omit the field for no tool-specific limit. | ## Cost and usage Worker tokens are billed at the selected model's standard rate. Each Subagent call appears at `usage.server_tool_use.subagent_requests`. `orq:sidekick` remains accepted as a legacy alias. # Web fetch server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/web-fetch Fetch text from public URLs during a model response with the orq:web_fetch server tool. The `orq:web_fetch` tool retrieves text from a URL chosen by the model. The **AI Gateway** checks the URL before fetching it and returns the extracted content to the model. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Summarize https://docs.example.com/release-notes" } ], "tools": [ { "type": "orq:web_fetch", "max_content_tokens": 4000, "allowed_domains": ["docs.example.com"] } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'Summarize https://docs.example.com/release-notes', }, ], tools: [ { type: 'orq:web_fetch', max_content_tokens: 4000, allowed_domains: ['docs.example.com'], }, ] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Summarize https://docs.example.com/release-notes", } ], tools=[ { "type": "orq:web_fetch", "max_content_tokens": 4000, "allowed_domains": ["docs.example.com"], } ], ) print(response.choices[0].message.content) ``` ## Configuration | Parameter | Type | Required | Default | Description | | -------------------- | --------- | -------- | ---------------------- | ----------------------------------------------------------------------------------------- | | `type` | string | Yes | | Must be `orq:web_fetch`. | | `engine` | string | No | `auto` | Fetch engine. Accepted values are `auto` and `jina`. | | `max_uses` | integer | No | Unlimited | Maximum fetches during the request. Set `0` or omit the field for no tool-specific limit. | | `max_content_tokens` | integer | No | No tool-specific limit | Truncate extracted content to approximately this many tokens. Minimum: 1. | | `allowed_domains` | string\[] | No | All domains | Only fetch URLs from the listed domains. | | `blocked_domains` | string\[] | No | None | Reject URLs from the listed domains. | ## URL restrictions The tool accepts public `http` and `https` URLs. It rejects loopback, link-local, private, and unspecified IP addresses. Domain rules are checked before the fetch runs. When a URL is rejected, the model receives the reason and can choose another URL. Rejected URLs do not use a `max_uses` slot. ## Usage Fetches that pass URL validation and the usage limit appear at `usage.server_tool_use.web_fetch_requests`. There is no separate server-tool charge for web fetch in this release. Use [Web search](/docs/ai-gateway/features/server-tools/web-search) when the URL is not known in advance. # Web search server tool Source: https://docs.orq.ai/docs/ai-gateway/features/server-tools/web-search Give a model access to current public web results through the orq:web_search server tool. The `orq:web_search` tool lets a model search the public web during a response. The **AI Gateway** runs each search and returns the results to the model, which decides how to use them in its answer. ## Quick start The examples use the client configuration from the [Server tools overview](/docs/ai-gateway/features/server-tools). ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "What are the latest changes to the EU AI Act?" } ], "tools": [ { "type": "orq:web_search", "max_results": 5, "max_uses": 3 } ] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const response = await client.chat.completions.create({ model: 'openai/gpt-5.4-mini', messages: [ { role: 'user', content: 'What are the latest changes to the EU AI Act?' }, ], tools: [{ type: 'orq:web_search', max_results: 5, max_uses: 3 }] as any, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ {"role": "user", "content": "What are the latest changes to the EU AI Act?"} ], tools=[ {"type": "orq:web_search", "max_results": 5, "max_uses": 3} ], ) print(response.choices[0].message.content) ``` The model writes the search query. Configuration on the tool entry controls the engine, result count, call count, and domains. ## Configuration | Parameter | Type | Required | Default | Description | | ------------------ | --------- | -------- | -------------- | ------------------------------------------------------------------------------------------ | | `type` | string | Yes | | Must be `orq:web_search`. | | `engine` | string | No | `auto` | Search engine. Accepted values are `auto` and `serper`. | | `max_results` | integer | No | Engine default | Maximum results returned per search. Accepted range: 1 to 25. | | `max_uses` | integer | No | Unlimited | Maximum searches during the request. Set `0` or omit the field for no tool-specific limit. | | `allowed_domains` | string\[] | No | All domains | Restrict results to the listed domains. | | `excluded_domains` | string\[] | No | None | Exclude results from the listed domains. | `max_results` is a cap. If the model asks for fewer results, the lower value is used. ## Cost and usage Each **Orq.ai** web search that starts execution costs \$0.005. The charge is included in the response cost when billing data is available. The number of searches appears at `usage.server_tool_use.web_search_requests`. Calls rejected by `max_uses` do not increment the counter. ## Related tools Use [Web fetch](/docs/ai-gateway/features/server-tools/web-fetch) when a prompt already contains the URL to read. Web search finds pages; web fetch retrieves a specific page. # LLM response streaming Source: https://docs.orq.ai/docs/ai-gateway/features/streaming Enable real-time streaming for LLM responses. Deliver incremental content for better UX with Server-Sent Events, React hooks, and error handling patterns. **Use Cases** * Chat UIs that show responses as they arrive, before generation completes. * Long-form generation (reports, code) where waiting for the full output hurts UX. * Agent workflows that surface reasoning steps or tool calls in real time. * Reducing perceived latency on slow models or large outputs. *** ## Quick Start Enable real-time response streaming for better user experience. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -N -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "input": "Write a story about space exploration", "stream": true }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const stream = await client.responses.create({ model: "openai/gpt-5.4", input: "Write a story about space exploration", stream: true, }); for await (const event of stream) { if (event.type === "response.output_text.delta") { process.stdout.write(event.delta); } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) stream = client.responses.create( model="openai/gpt-5.4", input="Write a story about space exploration", stream=True, ) for event in stream: if event.type == "response.output_text.delta": print(event.delta, end="", flush=True) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const stream = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [ { role: "user", content: "Write a story about space exploration" }, ], stream: true, }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; if (content) { process.stdout.write(content); } } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) stream = client.chat.completions.create( model="openai/gpt-5.4", messages=[{"role": "user", "content": "Write a story about space exploration"}], stream=True, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content is not None: print(chunk.choices[0].delta.content, end="", flush=True) ``` ## Configuration | Parameter | Type | Required | Description | | --------- | ------- | -------- | -------------------------- | | `stream` | boolean | Yes | Enable streaming responses | **All models support streaming**: no additional configuration needed. ## Response Format **Streaming chunks:** ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "response.output_text.delta", "delta": "Hello" } ``` ```json JSON (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} { "id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1677652288, "model": "openai/gpt-5.4", "choices": [ { "index": 0, "delta": { "content": "Hello" }, "finish_reason": null } ] } ``` **Final chunk:** ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "response.output_text.done" } ``` ```json JSON (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} { "choices": [ { "index": 0, "delta": {}, "finish_reason": "stop" } ] } ``` ## Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -N -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "input": "Write a detailed explanation of quantum computing", "stream": true }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -N -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "messages": [ { "role": "user", "content": "Write a detailed explanation of quantum computing" } ], "stream": true }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const stream = await client.responses.create({ model: "openai/gpt-5.4", input: "Write a detailed explanation of quantum computing", stream: true, }); for await (const event of stream) { if (event.type === "response.output_text.delta") { process.stdout.write(event.delta); } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) stream = client.responses.create( model="openai/gpt-5.4", input="Write a detailed explanation of quantum computing", stream=True, ) for event in stream: if event.type == "response.output_text.delta": print(event.delta, end="", flush=True) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const stream = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [ { role: "user", content: "Write a detailed explanation of quantum computing", }, ], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content || ""); } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) stream = client.chat.completions.create( model="openai/gpt-5.4", messages=[ { "role": "user", "content": "Write a detailed explanation of quantum computing", } ], stream=True, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content is not None: print(chunk.choices[0].delta.content, end="") ``` ## Stream Processing Patterns The examples in this section use the Chat Completions endpoint. The same patterns apply to the Responses API: replace `chat.completions.create(...)` with `responses.create(...)`, update the endpoint to `/v3/router/responses`, and handle `response.output_text.delta` events instead of `choices[0].delta.content`. ### Basic processing Accumulate deltas into a full string and detect completion via `finish_reason`. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const processStream = async (stream) => { let fullResponse = ""; for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; if (content) { fullResponse += content; console.log(content); // Real-time output } // Check for completion if (chunk.choices[0]?.finish_reason) { console.log(`\nStream finished: ${chunk.choices[0].finish_reason}`); break; } } return fullResponse; }; ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} def process_stream(stream) -> str: full_response = "" for chunk in stream: content = chunk.choices[0].delta.content or "" if chunk.choices else "" if content: full_response += content print(content, end="", flush=True) if chunk.choices and chunk.choices[0].finish_reason: print(f"\nStream finished: {chunk.choices[0].finish_reason}") break return full_response ``` ### With error handling Guard against network drops and unexpected errors by wrapping the stream loop in a try/except. The TypeScript example additionally resets a timeout on each chunk. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const updateUI = (content: string) => { process.stdout.write(content); }; // replace with your UI update logic const robustStreamProcessing = async (stream) => { try { let response = ""; const timeout = setTimeout(() => { throw new Error("Stream timeout"); }, 30000); for await (const chunk of stream) { clearTimeout(timeout); if (chunk.choices[0]?.delta?.content) { response += chunk.choices[0].delta.content; // Update UI with new content updateUI(chunk.choices[0].delta.content); } if (chunk.choices[0]?.finish_reason) { break; } } return response; } catch (error) { console.error("Streaming error:", error); throw error; } }; ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import sys def update_ui(content: str) -> None: sys.stdout.write(content) # replace with actual UI update logic def robust_stream_processing(stream) -> str: try: response = "" for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: response += chunk.choices[0].delta.content update_ui(chunk.choices[0].delta.content) if chunk.choices and chunk.choices[0].finish_reason: break return response except Exception as error: print(f"Streaming error: {error}", file=sys.stderr) raise ``` ## Function Calling with Streaming Stream tool calls as they're generated: ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const tools = [ { type: "function" as const, function: { name: "get_weather", description: "Get current weather", parameters: { type: "object", properties: { location: { type: "string" } }, required: ["location"], }, }, }, ]; const stream = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [{ role: "user", content: "What's the weather in Paris?" }], tools, stream: true, }); for await (const chunk of stream) { if (!chunk.choices.length) continue; const delta = chunk.choices[0].delta; if (delta.tool_calls?.[0]?.function?.arguments) { process.stdout.write(delta.tool_calls[0].function.arguments); } else if (delta.content) { process.stdout.write(delta.content); } } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get current weather", "parameters": { "type": "object", "properties": { "location": {"type": "string"} }, "required": ["location"] } } } ] stream = client.chat.completions.create( model="openai/gpt-5.4", messages=[{"role": "user", "content": "What's the weather in Paris?"}], tools=tools, stream=True ) for chunk in stream: if not chunk.choices: continue # Handle tool calls if chunk.choices[0].delta.tool_calls: tool_call = chunk.choices[0].delta.tool_calls[0] if tool_call.function.arguments: print(tool_call.function.arguments, end="") # Handle regular content elif chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="") ``` ## UI Integration Examples ### React hook for streaming Encapsulate streaming state in a hook so components receive `response` and `isStreaming` without managing the event loop themselves. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { useState, useCallback } from "react"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const useStreamingChat = () => { const [response, setResponse] = useState(""); const [isStreaming, setIsStreaming] = useState(false); const streamChat = useCallback(async (message) => { setIsStreaming(true); setResponse(""); try { const stream = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [{ role: "user", content: message }], stream: true, }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; if (content) { setResponse((prev) => prev + content); } if (chunk.choices[0]?.finish_reason) { setIsStreaming(false); break; } } } catch (error) { console.error("Streaming failed:", error); setIsStreaming(false); } }, []); return { response, isStreaming, streamChat }; }; ``` **Server-Sent Events (Browser):** ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const streamWithSSE = async (message: string): Promise => { const response = await fetch("/api/chat-stream", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message }), }); if (!response.ok || !response.body) { throw new Error(`Request failed: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); const output = document.getElementById("response")!; let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { if (line === "data: [DONE]") break; if (!line.startsWith("data: ")) continue; const data = JSON.parse(line.slice(6)); const content = data.choices[0]?.delta?.content; if (content) output.innerHTML += content; } } }; ``` ## Performance Optimization ### Chunk buffering Batching small chunks before flushing to the UI reduces render cycles and smooths perceived output. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} class StreamBuffer { private buffer: string; private flushInterval: number; private lastFlush: number; constructor(flushInterval = 50) { this.buffer = ""; this.flushInterval = flushInterval; this.lastFlush = Date.now(); } add(content: string): void { this.buffer += content; // Flush periodically or when buffer is large if ( Date.now() - this.lastFlush > this.flushInterval || this.buffer.length > 100 ) { this.flush(); } } flush(): void { if (this.buffer) { this.onFlush(this.buffer); this.buffer = ""; this.lastFlush = Date.now(); } } onFlush(content: string): void { // Override this method console.log(content); } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import time import sys class StreamBuffer: def __init__(self, flush_interval: float = 0.05): self._buffer = "" self._flush_interval = flush_interval self._last_flush = time.time() def add(self, content: str) -> None: self._buffer += content if time.time() - self._last_flush > self._flush_interval or len(self._buffer) > 100: self.flush() def flush(self) -> None: if self._buffer: self.on_flush(self._buffer) self._buffer = "" self._last_flush = time.time() def on_flush(self, content: str) -> None: sys.stdout.write(content) # override in subclass ``` ### Memory management For long completions, cap accumulation to avoid unbounded memory growth. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const processLargeStream = async (stream, maxMemory = 1000000) => { let totalLength = 0; const chunks = []; for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; if (content) { totalLength += content.length; chunks.push(content); // Prevent memory overflow if (totalLength > maxMemory) { console.warn("Stream too large, truncating"); break; } } if (chunk.choices[0]?.finish_reason) { break; } } return chunks.join(""); }; ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import sys def process_large_stream(stream, max_memory: int = 1_000_000) -> str: total_length = 0 chunks = [] for chunk in stream: content = chunk.choices[0].delta.content or "" if chunk.choices else "" if content: total_length += len(content) chunks.append(content) if total_length > max_memory: print("Stream too large, truncating", file=sys.stderr) break if chunk.choices and chunk.choices[0].finish_reason: break return "".join(chunks) ``` ## Best Practices ### Stream management * Set reasonable timeouts (30-60 seconds). * Implement proper error boundaries. * Handle network interruptions gracefully. * Provide user cancellation options. ### UI/UX considerations * Show typing indicators during streaming. * Allow users to stop generation. * Buffer small chunks for smoother display. * Handle rapid updates efficiently. ### Error recovery example ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const streamWithRetry = async (input: string, maxRetries = 3) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const stream = await client.responses.create({ model: "openai/gpt-5.4", input, stream: true, }); let fullResponse = ""; for await (const event of stream) { if (event.type === "response.output_text.delta") { fullResponse += event.delta; process.stdout.write(event.delta); } } return fullResponse; } catch (error) { if (attempt === maxRetries) throw error; console.log(`Stream attempt ${attempt} failed, retrying...`); await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); } } }; ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os import sys import time client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) def stream_responses_with_retry(input_text: str, max_retries: int = 3) -> str: for attempt in range(1, max_retries + 1): try: stream = client.responses.create( model="openai/gpt-5.4", input=input_text, stream=True, ) full_response = "" for event in stream: if event.type == "response.output_text.delta": full_response += event.delta sys.stdout.write(event.delta) sys.stdout.flush() return full_response except Exception: if attempt == max_retries: raise print(f"Stream attempt {attempt} failed, retrying...") time.sleep(attempt) return "" ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const streamWithRetry = async (messages: { role: string; content: string }[], maxRetries = 3) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const stream = await client.chat.completions.create({ model: "openai/gpt-5.4", messages, stream: true, }); let fullResponse = ""; for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; if (content) { fullResponse += content; process.stdout.write(content); } } return fullResponse; } catch (error) { if (attempt === maxRetries) throw error; console.log(`Stream attempt ${attempt} failed, retrying...`); await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); } } }; ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os import sys import time client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) def stream_chat_with_retry(messages: list, max_retries: int = 3) -> str: for attempt in range(1, max_retries + 1): try: stream = client.chat.completions.create( model="openai/gpt-5.4", messages=messages, stream=True, ) full_response = "" for chunk in stream: content = chunk.choices[0].delta.content or "" if chunk.choices else "" if content: full_response += content sys.stdout.write(content) sys.stdout.flush() return full_response except Exception: if attempt == max_retries: raise print(f"Stream attempt {attempt} failed, retrying...") time.sleep(attempt) return "" ``` ## Troubleshooting **Stream cuts off unexpectedly** * Check network stability. * Verify timeout settings. * Monitor for rate limiting. * Check model-specific limits. **Slow streaming performance** * Optimize chunk processing. * Reduce buffer flush frequency. * Check network latency. * Consider model selection. **400 for a large non-streaming request** For Anthropic requests, a non-streaming request whose `max_tokens` implies more than 10 minutes of generation returns `400` with `type: "invalid_request_error"`. Set `stream: true` for long outputs or reduce `max_tokens`. **Memory issues** * Implement chunk size limits. * Use streaming parsers. * Clear processed chunks. * Monitor memory usage. ## Limitations | Limitation | Impact | Workaround | | ------------------------ | ----------------------- | ----------------------------- | | **Network interruption** | Stream breaks | Implement reconnection logic | | **Processing overhead** | Slight performance cost | Optimize chunk handling | | **Model variations** | Different chunk sizes | Handle variable chunk lengths | | **Rate limiting** | Stream throttling | Implement backoff strategies | ## Advanced Features The examples in this section use the Chat Completions endpoint. The same patterns apply to the Responses API: replace `chat.completions.create(...)` with `responses.create(...)`. For cURL, use `/v3/router/responses`. ### Stream with other Gateway features **AI Gateway** features like caching, timeouts, and deployment names compose directly with streaming: add them to the same request object. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const advancedStream = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [{ role: "user", content: "Explain machine learning" }], stream: true, name: "StreamingBot-v1", cache: { type: "exact_match", ttl: 3600 }, timeout: { call_timeout: 30000 }, }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) stream = client.chat.completions.create( model="openai/gpt-5.4", messages=[{"role": "user", "content": "Explain machine learning"}], stream=True, extra_body={ "name": "StreamingBot-v1", "cache": {"type": "exact_match", "ttl": 3600}, "timeout": {"call_timeout": 30000}, }, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) ``` ### Parallel streaming Fire multiple streams concurrently using `Promise.all` in TypeScript and `asyncio.gather` in Python to get independent responses without waiting for each to finish. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const processQuery = async (query) => { const stream = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [{ role: "user", content: query }], stream: true, }); let fullResponse = ""; for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; if (content) fullResponse += content; } return fullResponse; }; const parallelStreaming = async (queries) => Promise.all(queries.map(processQuery)); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import AsyncOpenAI import asyncio import os client = AsyncOpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) async def process_query(query: str) -> str: stream = await client.chat.completions.create( model="openai/gpt-5.4", messages=[{"role": "user", "content": query}], stream=True, ) full_response = "" async for chunk in stream: content = chunk.choices[0].delta.content or "" if chunk.choices else "" if content: full_response += content return full_response async def parallel_streaming(queries: list[str]) -> list[str]: return await asyncio.gather(*[process_query(query) for query in queries]) ``` # Structured outputs with JSON schema Source: https://docs.orq.ai/docs/ai-gateway/features/structured-outputs Generate type-safe JSON responses with guaranteed schema compliance. Use Zod or Pydantic for validated LLM outputs with full TypeScript/Python support. **Use Cases** * Extracting typed fields from unstructured text (invoices, emails, support tickets). * Pipelines where downstream code requires a guaranteed JSON schema. * Classification tasks with a controlled output set (enum values, boolean flags). * Generating config objects or API payloads directly from natural language. *** ## Quick Start Generate structured JSON responses with guaranteed schema compliance. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Extract info: John Doe, 30, john@example.com, knows Python and React", "text": { "format": { "type": "json_schema", "name": "user", "schema": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "email": {"type": "string"}, "skills": {"type": "array", "items": {"type": "string"}} }, "required": ["name", "age", "email", "skills"], "additionalProperties": false } } } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { z } from "zod"; import { zodTextFormat } from "openai/helpers/zod"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const UserSchema = z.object({ name: z.string(), age: z.number(), email: z.string(), skills: z.array(z.string()), }); const response = await client.responses.parse({ model: "openai/gpt-5.6-sol", input: "Extract info: John Doe, 30, [email protected], knows Python and React", text: { format: zodTextFormat(UserSchema, "user") }, }); const user = response.output_parsed; console.log(user.name); console.log(user.skills); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from pydantic import BaseModel import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class UserSchema(BaseModel): name: str age: int email: str skills: list[str] response = client.responses.parse( model="openai/gpt-5.6-sol", input="Extract info: John Doe, 30, [email protected], knows Python and React", text_format=UserSchema, ) user = response.output_parsed print(user.name) print(user.skills) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { z } from "zod"; import { zodResponseFormat } from "openai/helpers/zod"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const UserSchema = z.object({ name: z.string(), age: z.number(), email: z.string(), skills: z.array(z.string()), }); const response = await client.chat.completions.parse({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: "Extract info: John Doe, 30, [email protected], knows Python and React", }, ], response_format: zodResponseFormat(UserSchema, "user"), }); const user = response.choices[0].message.parsed; console.log(user.name); console.log(user.skills); ``` ## Configuration Options ### Response Format Types | Type | Description | | --------------- | ------------------------------------------ | | `"text"` | Default response format for text responses | | `"json_object"` | Basic JSON output (no schema validation) | | `"json_schema"` | Structured JSON with schema validation | ### Text Mode (Default) | Parameter | Type | Description | | --------- | -------- | ---------------------------------------- | | `type` | `"text"` | Default response format for text outputs | ### JSON Schema Mode | Parameter | Type | Description | | -------------------- | --------------- | ------------------------ | | `type` | `"json_schema"` | Enable schema validation | | `json_schema.name` | string | Schema name/identifier | | `json_schema.schema` | object | JSON Schema definition | ### Simple JSON Mode | Parameter | Type | Description | | --------- | --------------- | ----------------------------- | | `type` | `"json_object"` | Basic JSON output (no schema) | ## Schema Examples ### Simple Data Extraction ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "json_schema", "json_schema": { "name": "contact_info", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "phone": { "type": "string" }, "company": { "type": "string" } }, "required": ["name", "email"] } } } ``` ### Complex Nested Structure ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "json_schema", "json_schema": { "name": "product_analysis", "schema": { "type": "object", "properties": { "product": { "type": "object", "properties": { "name": { "type": "string" }, "price": { "type": "number" }, "category": { "type": "string" } } }, "features": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "importance": { "type": "integer", "minimum": 1, "maximum": 5 } } } }, "summary": { "type": "string" } }, "required": ["product", "features", "summary"] } } } ``` ## Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Extract user information from: John Doe, 30 years old, john@example.com, Software Engineer", "text": { "format": { "type": "json_schema", "name": "user_info", "schema": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "email": {"type": "string"}, "occupation": {"type": "string"} }, "required": ["name", "age", "email", "occupation"], "additionalProperties": false } } } }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [ { "role": "user", "content": "Extract user information from: John Doe, 30 years old, john@example.com, Software Engineer" } ], "response_format": { "type": "json_schema", "json_schema": { "name": "user_info", "schema": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "email": {"type": "string"}, "occupation": {"type": "string"} }, "required": ["name", "age", "email", "occupation"] } } } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { z } from "zod"; import { zodTextFormat } from "openai/helpers/zod"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const UserInfoSchema = z.object({ name: z.string(), age: z.number(), email: z.string(), occupation: z.string(), }); const response = await client.responses.parse({ model: "openai/gpt-5.6-sol", input: "Extract user information from: John Doe, 30 years old, [email protected], Software Engineer", text: { format: zodTextFormat(UserInfoSchema, "user_info") }, }); const user = response.output_parsed; console.log(`Name: ${user.name}`); console.log(`Occupation: ${user.occupation}`); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from pydantic import BaseModel import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class UserInfo(BaseModel): name: str age: int email: str occupation: str response = client.responses.parse( model="openai/gpt-5.6-sol", input="Extract user information from: John Doe, 30 years old, [email protected], Software Engineer", text_format=UserInfo, ) user = response.output_parsed print(f"Name: {user.name}") print(f"Occupation: {user.occupation}") ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { z } from "zod"; import { zodResponseFormat } from "openai/helpers/zod"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const UserInfoSchema = z.object({ name: z.string(), age: z.number(), email: z.string(), occupation: z.string(), }); const response = await client.chat.completions.parse({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: "Extract user information from: John Doe, 30 years old, [email protected], Software Engineer", }, ], response_format: zodResponseFormat(UserInfoSchema, "user_info"), }); const user = response.choices[0].message.parsed; console.log(`Name: ${user.name}`); console.log(`Occupation: ${user.occupation}`); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from pydantic import BaseModel import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class UserInfo(BaseModel): name: str age: int email: str occupation: str response = client.chat.completions.parse( model="openai/gpt-5.6-sol", messages=[ { "role": "user", "content": "Extract user information from: John Doe, 30 years old, [email protected], Software Engineer", } ], response_format=UserInfo, ) user = response.choices[0].message.parsed print(f"Name: {user.name}") print(f"Occupation: {user.occupation}") ``` ## Common Use Cases The examples in this section use the Chat Completions endpoint. Structured outputs work identically with the Responses API: use `responses.create()` with `text: { format: { type: "json_schema", ... } }` instead of `response_format`. ### Data Extraction ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from pydantic import BaseModel from typing import List import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class LineItem(BaseModel): description: str quantity: int unit_price: float total: float class InvoiceData(BaseModel): invoice_number: str date: str vendor: str total_amount: float line_items: List[LineItem] response = client.chat.completions.parse( model="openai/gpt-5.6-sol", messages=[{ "role": "user", "content": "Extract invoice data from this text: [invoice text]" }], response_format=InvoiceData ) ``` ### Content Analysis ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { z } from "zod"; import { zodResponseFormat } from "openai/helpers/zod"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const SentimentSchema = z.object({ sentiment: z.enum(["positive", "negative", "neutral"]), confidence: z.number().min(0).max(1), key_phrases: z.array(z.string()), summary: z.string(), }); const response = await client.chat.completions.parse({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: "Analyze sentiment of this review: [review text]", }, ], response_format: zodResponseFormat(SentimentSchema, "sentiment_analysis"), }); ``` ### Form Generation ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from pydantic import BaseModel, Field from typing import List import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class SurveyQuestion(BaseModel): question: str type: str options: List[str] class SurveyForm(BaseModel): title: str description: str questions: List[SurveyQuestion] = Field( ..., description="List of survey questions with type and options" ) estimated_duration: int = Field(description="Minutes to complete") response = client.chat.completions.parse( model="openai/gpt-5.6-sol", messages=[{ "role": "user", "content": "Create a customer satisfaction survey for a restaurant" }], response_format=SurveyForm ) ``` ## Advanced Patterns ### Conditional Schemas ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { z } from "zod"; const ResponseSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("success"), data: z.object({ result: z.string(), metadata: z.record(z.any()), }), }), z.object({ type: z.literal("error"), error: z.object({ code: z.string(), message: z.string(), }), }), ]); ``` ### Dynamic Schema Generation ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from typing import List def create_schema_for_fields(fields: List[str]): properties = {} for field in fields: properties[field] = {"type": "string"} return { "type": "object", "properties": properties, "required": fields } # Generate schema based on user input fields = ["name", "email", "department"] schema = create_schema_for_fields(fields) ``` ### Validation and Error Handling ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from pydantic import BaseModel import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) prompt = "Extract the following information: John Doe is 30 years old." class MySchema(BaseModel): name: str age: int try: response = client.chat.completions.parse( model="openai/gpt-5.6-sol", messages=[{"role": "user", "content": prompt}], response_format=MySchema ) if response.choices[0].message.parsed: data = response.choices[0].message.parsed # Process validated data else: # Handle parsing failure print("Failed to parse response") except Exception as e: print(f"Error: {e}") # Fallback to regular completion ``` ## Best Practices **Schema design:** * Use descriptive field names. * Add field descriptions for better results. * Mark essential fields as required. * Use appropriate data types and constraints. **Error handling:** ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { z } from "zod"; import { zodResponseFormat } from "openai/helpers/zod"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const parseWithFallback = async (prompt: string, schema: z.ZodTypeAny) => { try { const response = await client.chat.completions.parse({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: prompt }], response_format: zodResponseFormat(schema, "data"), }); return response.choices[0].message.parsed; } catch (error) { console.warn("Structured parsing failed, trying JSON mode"); // Fallback to basic JSON mode const fallback = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: `${prompt}\n\nRespond with valid JSON only.`, }, ], response_format: { type: "json_object" }, }); const content = fallback.choices[0].message.content; if (!content) throw new Error("Empty fallback response"); return JSON.parse(content); } }; ``` **Performance optimization:** ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} # Cache schemas for reuse schema_cache = {} def create_schema(schema_name): ... # replace with your schema factory def get_cached_schema(schema_name): if schema_name not in schema_cache: schema_cache[schema_name] = create_schema(schema_name) return schema_cache[schema_name] ``` ## Troubleshooting **Schema validation fails** * Simplify complex nested structures. * Ensure required fields are clearly specified. * Check field types match expected data. * Add field descriptions for clarity. **Inconsistent outputs** * Use more specific prompts. * Add examples in the prompt. * Increase model temperature for creativity. * Switch to a more capable model. **Performance issues** * Reduce schema complexity. * Cache schema definitions. * Use appropriate models for task complexity. * Consider breaking large schemas into smaller ones. ## Limitations | Limitation | Impact | Workaround | | --------------------- | ------------------------------- | -------------------------------- | | **Schema complexity** | Large schemas may fail | Break into smaller schemas | | **Model support** | Not all models support schemas | Use JSON mode as fallback | | **Nested depth** | Deep nesting may cause issues | Flatten structures when possible | | **Array validation** | Complex array items challenging | Simplify item schemas | | **Performance cost** | Schema validation adds latency | Cache and optimize schemas | ## Integration Examples ### Database Integration ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from pydantic import BaseModel from typing import Optional from datetime import datetime import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class DatabaseRecord(BaseModel): id: Optional[int] = None name: str email: str created_at: Optional[datetime] = None # Generate structured data response = client.chat.completions.parse( model="openai/gpt-5.6-sol", messages=[{"role": "user", "content": "Create user record"}], response_format=DatabaseRecord ) # Direct database insertion (example using SQLAlchemy) record = response.choices[0].message.parsed # session.add(record) # session.commit() ``` ### API Integration ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import { z } from "zod"; import { zodResponseFormat } from "openai/helpers/zod"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const ApiResponseSchema = z.object({ status: z.enum(["success", "error"]), data: z.string(), message: z.string().nullable().optional(), }); const generateApiResponse = async (query) => { const response = await client.chat.completions.parse({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: query }], response_format: zodResponseFormat(ApiResponseSchema, "api_response"), }); // Return structured API response return response.choices[0].message.parsed; }; ``` # Request timeouts Source: https://docs.orq.ai/docs/ai-gateway/features/timeouts Set maximum LLM request duration to prevent hanging calls with automatic fallback. **Use Cases** * Preventing slow models from blocking user-facing requests indefinitely. * Setting different limits for interactive (short) vs. batch (long) workloads. * Triggering fallback logic when a provider exceeds an acceptable wait time. * Enforcing response-time SLAs on latency-sensitive features. *** ## Quick Start Set maximum request duration to prevent hanging requests. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Summarize AI trends for 2024", "timeout": {"call_timeout": 30000} }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Summarize AI trends for 2024", timeout: { call_timeout: 30000, }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Summarize AI trends for 2024", extra_body={"timeout": {"call_timeout": 30000}}, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "Summarize AI trends for 2024" }], timeout: { call_timeout: 30000, }, }); ``` ## Configuration | Parameter | Type | Required | Description | | -------------- | ------ | -------- | -------------------------------------- | | `call_timeout` | number | Yes | Maximum execution time in milliseconds | **Timeout applies to:** * Request processing time. * Model generation time. * Network transfer time. * All fallback attempts (each gets same timeout). ## Recommended Values | Use Case | Timeout (ms) | Reason | | ----------------------- | ------------- | ------------------------------ | | **Chat applications** | `15000` (15s) | User expectation for responses | | **Real-time features** | `5000` (5s) | Immediate feedback required | | **Batch processing** | `60000` (60s) | Complex analysis tasks | | **Streaming responses** | `30000` (30s) | Longer generation time | | **Development/testing** | `10000` (10s) | Fast iteration cycles | ## Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Summarize the latest trends in artificial intelligence for 2024", "timeout": {"call_timeout": 30000} }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Summarize the latest trends in artificial intelligence for 2024" } ], "timeout": {"call_timeout": 30000} }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Summarize the latest trends in artificial intelligence for 2024", timeout: { call_timeout: 30000, }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Summarize the latest trends in artificial intelligence for 2024", extra_body={"timeout": {"call_timeout": 30000}}, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.4-mini", messages: [ { role: "user", content: "Summarize the latest trends in artificial intelligence for 2024", }, ], timeout: { call_timeout: 30000, }, }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.chat.completions.create( model="openai/gpt-5.4-mini", messages=[ { "role": "user", "content": "Summarize the latest trends in artificial intelligence for 2024", } ], extra_body={"timeout": {"call_timeout": 30000}}, ) ``` ## Error Handling ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); try { const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Explain quantum computing", timeout: { call_timeout: 15000 }, }); console.log(response.output_text); } catch (error) { if (error instanceof OpenAI.APIConnectionTimeoutError) { console.log('Request timed out - try increasing timeout or using faster model'); } } ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); try { const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Explain quantum computing" }], timeout: { call_timeout: 15000 } }); } catch (error) { if (error instanceof OpenAI.APIConnectionTimeoutError) { console.log('Request timed out - try increasing timeout or using faster model'); // Implement fallback behavior } } ``` ## Best Practices **Timeout selection:** * Set based on user experience requirements. * Consider model complexity and prompt length. * Factor in network latency (add 2-5s buffer). * Test with realistic prompts and data. **Environment-specific timeouts:** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const timeouts = { development: 10000, // Fast feedback during dev staging: 20000, // Realistic testing production: 30000, // Conservative for reliability }; ``` **Progressive timeouts:** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Start with short timeout, increase for retries const attempts = [ { timeout: 10000, model: "fast-model" }, { timeout: 20000, model: "standard-model" }, { timeout: 30000, model: "comprehensive-model" }, ]; ``` ## Fallback Integration Timeouts work seamlessly with fallbacks: ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "timeout": { "call_timeout": 15000 }, "fallbacks": [ { "model": "openai/gpt-5.6-sol" }, { "model": "openai/gpt-5.4-mini" } ] } ``` **Total possible time:** `timeout × (1 + fallback_count)` * Primary + 2 fallbacks with 15s timeout = up to 45s total. ## Troubleshooting **Frequent timeouts** * Increase timeout value. * Use faster models (gpt-5.4-mini vs gpt-5.6-sol). * Reduce prompt complexity/length. * Check provider status for slowdowns. **User experience issues** * Set timeout based on user expectations. * Show loading states for longer operations. * Implement progressive enhancement. * Consider async processing for long tasks. **Performance optimization** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Monitor timeout patterns const timeoutMetrics = { averageResponseTime: 0, timeoutRate: 0, responseTimesByModel: {}, optimalTimeout: 0, // 95th percentile + buffer }; ``` ## Advanced Patterns **Dynamic timeout adjustment:** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const getDynamicTimeout = (promptLength, modelComplexity) => { const baseTimeout = 10000; const promptFactor = Math.min(promptLength / 1000, 3); // Max 3x for long prompts const modelFactor = modelComplexity === "simple" ? 1 : 2; return baseTimeout * promptFactor * modelFactor; }; ``` **Timeout with streaming:** ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "stream": true, "timeout": { "call_timeout": 30000 } } ``` **Circuit breaker pattern:** ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} class CircuitBreaker { timeout: number; failureCount: number; failureThreshold: number; state: "CLOSED" | "OPEN" | "HALF_OPEN"; constructor(timeout: number, failureThreshold = 5) { this.timeout = timeout; this.failureCount = 0; this.failureThreshold = failureThreshold; this.state = "CLOSED"; } async call(requestFn) { if (this.state === "OPEN") { throw new Error("Circuit breaker is OPEN"); } try { const result = await requestFn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } } ``` ## Limitations * **Fixed timeout**: Same timeout applies to all requests. * **No granular control**: Cannot set different timeouts for different operations. * **Fallback multiplication**: Each fallback gets the same timeout duration. * **Provider variations**: Different providers have different baseline response times. * **Streaming considerations**: Streaming responses may need longer timeouts. ## Monitoring Key metrics to track: * **Timeout rate**: % of requests that timeout. * **Average response time**: Baseline performance. * **95th percentile**: For setting optimal timeouts. * **Timeout impact**: User experience degradation. * **Model performance**: Response times by model. ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Example monitoring const metrics = { totalRequests: 0, timeouts: 0, responseTimes: [] as number[], recommendedTimeout: 0, }; const calculatePercentile = (arr: number[], p: number): number => { const sorted = [...arr].sort((a, b) => a - b); return sorted[Math.floor((p / 100) * sorted.length)] ?? 0; }; const monitorTimeouts = (responseTime: number, wasTimeout: boolean) => { metrics.totalRequests++; if (wasTimeout) { metrics.timeouts++; } else { metrics.responseTimes.push(responseTime); } // Calculate optimal timeout (95th percentile + 5s buffer) const p95 = calculatePercentile(metrics.responseTimes, 95); metrics.recommendedTimeout = p95 + 5000; }; ``` # Tool calling and function execution Source: https://docs.orq.ai/docs/ai-gateway/features/tool-calling Enable LLMs to call external functions with structured parameters. Build AI agents that interact with APIs, databases, and external services. **Use Cases** * Connecting models to live data (databases, calendars, internal APIs) without prompt hacks. * Agents that take actions on behalf of users: create tickets, send emails, run queries. * Multi-step workflows where the model decides which tools to invoke and in what order. * Replacing brittle regex parsing with structured function calls for data extraction. *** ## Quick Start Enable AI models to call external functions with structured parameters. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "input": "What is the weather in NYC?", "tools": [{ "type": "function", "name": "get_weather", "description": "Get current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City and state" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] } }, "required": ["location"] } }], "tool_choice": "auto" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); async function getWeather(location: string, unit = "celsius") { // Your implementation here - fetch from a weather API, etc. return { location, temperature: 22, unit, conditions: "sunny" }; } const tools = [ { type: "function" as const, name: "get_weather", description: "Get current weather for a location", parameters: { type: "object", properties: { location: { type: "string", description: "City and state" }, unit: { type: "string", enum: ["celsius", "fahrenheit"] }, }, required: ["location"], }, }, ]; const response = await client.responses.create({ model: "openai/gpt-5.4", input: "What's the weather in NYC?", tools, tool_choice: "auto", }); const toolCall = response.output.find((item) => item.type === "function_call"); if (toolCall && toolCall.type === "function_call") { const args = JSON.parse(toolCall.arguments); const result = await getWeather(args.location, args.unit); const callId = toolCall.call_id; // Pattern: previous_response_id - the router maintains conversation state server-side const finalResponse = await client.responses.create({ model: "openai/gpt-5.4", previous_response_id: response.id, input: [{ type: "function_call_output", call_id: callId, output: JSON.stringify(result), }], }); console.log(finalResponse.output_text); } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import json import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) def get_weather(location: str, unit: str = "celsius") -> dict: # Your implementation here - fetch from a weather API, etc. return {"location": location, "temperature": 22, "unit": unit, "conditions": "sunny"} tools = [ { "type": "function", "name": "get_weather", "description": "Get current weather for a location", "parameters": { "type": "object", "properties": { "location": {"type": "string", "description": "City and state"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["location"], }, } ] response = client.responses.create( model="openai/gpt-5.4", input="What's the weather in NYC?", tools=tools, tool_choice="auto", ) tool_call = next((item for item in response.output if item.type == "function_call"), None) if tool_call: args = json.loads(tool_call.arguments) result = get_weather(args["location"], args.get("unit", "celsius")) # Pattern: previous_response_id - the router maintains conversation state server-side final_response = client.responses.create( model="openai/gpt-5.4", previous_response_id=response.id, input=[{ "type": "function_call_output", "call_id": tool_call.call_id, "output": json.dumps(result), }], ) print(final_response.output_text) ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "messages": [{"role": "user", "content": "What is the weather in NYC?"}], "tools": [{ "type": "function", "function": { "name": "get_weather", "description": "Get current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City and state" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] } }, "required": ["location"] } } }], "tool_choice": "auto" }' ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const tools = [ { type: "function" as const, function: { name: "get_weather", description: "Get current weather for a location", parameters: { type: "object", properties: { location: { type: "string", description: "City and state" }, unit: { type: "string", enum: ["celsius", "fahrenheit"] }, }, required: ["location"], }, }, }, ]; async function getWeather(location: string, unit = "celsius") { // Your implementation here - fetch from a weather API, etc. return { location, temperature: 22, unit, conditions: "sunny" }; } const response = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [{ role: "user", content: "What's the weather in NYC?" }], tools, tool_choice: "auto", }); if (response.choices[0].message.tool_calls) { const toolCall = response.choices[0].message.tool_calls[0]; const args = JSON.parse(toolCall.function.arguments); const result = await getWeather(args.location, args.unit); const finalResponse = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: [ { role: "user", content: "What's the weather in NYC?" }, response.choices[0].message, { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result), }, ], }); console.log(finalResponse.choices[0].message.content); } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import json import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) def get_weather(location: str, unit: str = "celsius") -> dict: # Your implementation here - fetch from a weather API, etc. return {"location": location, "temperature": 22, "unit": unit, "conditions": "sunny"} tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get current weather for a location", "parameters": { "type": "object", "properties": { "location": {"type": "string", "description": "City and state"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["location"], }, }, } ] messages = [{"role": "user", "content": "What's the weather in NYC?"}] response = client.chat.completions.create( model="openai/gpt-5.4", messages=messages, tools=tools, tool_choice="auto", ) if response.choices[0].message.tool_calls: tool_call = response.choices[0].message.tool_calls[0] args = json.loads(tool_call.function.arguments) result = get_weather(args["location"], args.get("unit", "celsius")) messages.append(response.choices[0].message) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result), }) final_response = client.chat.completions.create( model="openai/gpt-5.4", messages=messages, ) print(final_response.choices[0].message.content) ``` ## Configuration ### Tool Definition **Responses API** (`/v3/router/responses`): flat shape: | Parameter | Type | Required | Description | | ------------- | ------------ | -------- | -------------------------- | | `type` | `"function"` | Yes | Tool type | | `name` | string | Yes | Function identifier | | `description` | string | Yes | What the function does | | `parameters` | object | Yes | JSON Schema for parameters | **Chat Completions** (`/v3/router/chat/completions`): nested `function` wrapper: | Parameter | Type | Required | Description | | ---------------------- | ------------ | -------- | -------------------------- | | `type` | `"function"` | Yes | Tool type | | `function.name` | string | Yes | Function identifier | | `function.description` | string | Yes | What the function does | | `function.parameters` | object | Yes | JSON Schema for parameters | ### Tool Choice Options | Value | Behavior | | --------------------------------------------------- | ------------------------------- | | `"auto"` | Model decides when to use tools | | `"none"` | Disable tool usage | | `"required"` | Force tool usage | | `{type: "function", function: {name: "tool_name"}}` | Force specific tool | ### Tool Message Format This format applies to the Chat Completions endpoint (`/v3/router/chat/completions`). On the Responses API, tool results use `type: "function_call_output"`, `call_id`, and `output` instead. When providing tool results back to the model, use the `tool` role: | Parameter | Type | Required | Description | | -------------- | -------------- | -------- | --------------------------------------------- | | `role` | `"tool"` | Yes | Message role for tool results | | `tool_call_id` | string \| null | Yes | ID of the tool call being responded to | | `content` | string | Yes | JSON-stringified result of the tool execution | The `tool_call_id` can be `null` in certain scenarios, such as when tool results are being provided without a corresponding tool call from the model, or when working with providers that don't require tool call IDs. ## Code examples ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "input": "What is the weather like in San Francisco?", "tools": [ { "type": "function", "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "The temperature unit" } }, "required": ["location"] } } ], "tool_choice": "auto" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const tools = [ { type: "function" as const, name: "get_weather", description: "Get the current weather for a location", parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g. San Francisco, CA", }, unit: { type: "string", enum: ["celsius", "fahrenheit"], description: "The temperature unit", }, }, required: ["location"], }, }, ]; const response = await client.responses.create({ model: "openai/gpt-5.4", input: "What's the weather like in San Francisco?", tools, tool_choice: "auto", }); const toolCall = response.output.find((item) => item.type === "function_call"); if (toolCall && toolCall.type === "function_call") { const args = JSON.parse(toolCall.arguments); const weatherResult = { temperature: 72, unit: "fahrenheit", description: "Sunny with light clouds", }; // Pattern: spread response.output - the full conversation history is sent client-side const finalResponse = await client.responses.create({ model: "openai/gpt-5.4", input: [ ...response.output, { type: "function_call_output", call_id: toolCall.call_id, output: JSON.stringify(weatherResult), }, ], }); console.log(finalResponse.output_text); } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import json import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) tools = [ { "type": "function", "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA", }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "The temperature unit", }, }, "required": ["location"], }, } ] response = client.responses.create( model="openai/gpt-5.4", input="What's the weather like in San Francisco?", tools=tools, tool_choice="auto", ) tool_call = next((item for item in response.output if item.type == "function_call"), None) if tool_call: arguments = json.loads(tool_call.arguments) weather_result = { "temperature": 72, "unit": "fahrenheit", "description": "Sunny with light clouds", } final_response = client.responses.create( model="openai/gpt-5.4", input=[ *response.output, { "type": "function_call_output", "call_id": tool_call.call_id, "output": json.dumps(weather_result), }, ], ) print(final_response.output_text) ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "messages": [ { "role": "user", "content": "What is the weather like in San Francisco?" } ], "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "The temperature unit" } }, "required": ["location"] } } } ], "tool_choice": "auto" }' ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const tools = [ { type: "function" as const, function: { name: "get_weather", description: "Get the current weather for a location", parameters: { type: "object", properties: { location: { type: "string", description: "The city and state, e.g. San Francisco, CA", }, unit: { type: "string", enum: ["celsius", "fahrenheit"], description: "The temperature unit", }, }, required: ["location"], }, }, }, ]; const messages = [ { role: "user" as const, content: "What's the weather like in San Francisco?" }, ]; const response = await client.chat.completions.create({ model: "openai/gpt-5.4", messages, tools, tool_choice: "auto", }); if (response.choices[0].message.tool_calls) { const toolCall = response.choices[0].message.tool_calls[0]; const args = JSON.parse(toolCall.function.arguments); const weatherResult = { temperature: 72, unit: "fahrenheit", description: "Sunny with light clouds", }; messages.push(response.choices[0].message); messages.push({ role: "tool" as const, tool_call_id: toolCall.id, content: JSON.stringify(weatherResult), }); const finalResponse = await client.chat.completions.create({ model: "openai/gpt-5.4", messages, }); console.log(finalResponse.choices[0].message.content); } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import json import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA", }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "The temperature unit", }, }, "required": ["location"], }, }, } ] messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}] response = client.chat.completions.create( model="openai/gpt-5.4", messages=messages, tools=tools, tool_choice="auto", ) if response.choices[0].message.tool_calls: tool_call = response.choices[0].message.tool_calls[0] arguments = json.loads(tool_call.function.arguments) weather_result = { "temperature": 72, "unit": "fahrenheit", "description": "Sunny with light clouds", } messages.append(response.choices[0].message) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(weather_result), }) final_response = client.chat.completions.create( model="openai/gpt-5.4", messages=messages, ) print(final_response.choices[0].message.content) ``` ## Function Execution Patterns ### Basic Tool Handler A registry that maps tool names to handler functions, replacing per-call switch statements with a single dispatch path. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} class ToolHandler { constructor() { this.tools = new Map(); } register(name, func, schema) { this.tools.set(name, { func, schema }); } async execute(toolCall) { const tool = this.tools.get(toolCall.function.name); if (!tool) throw new Error(`Unknown tool: ${toolCall.function.name}`); const args = JSON.parse(toolCall.function.arguments); const result = await tool.func(args); return { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result), }; } } // Stubs - replace with your actual implementations const weatherSchema = { type: "object", properties: { location: { type: "string" } }, required: ["location"] }; const searchSchema = { type: "object", properties: { query: { type: "string" } }, required: ["query"] }; const getWeatherAPI = async ({ location }) => ({ temperature: 72, condition: "sunny", location }); const searchWebAPI = async ({ query }) => ({ results: [`Result for: ${query}`] }); const response = { choices: [{ message: { tool_calls: [] } }] }; const handler = new ToolHandler(); handler.register("get_weather", getWeatherAPI, weatherSchema); handler.register("search_web", searchWebAPI, searchSchema); // Execute tool calls const toolResults = await Promise.all( response.choices[0].message.tool_calls.map((call) => handler.execute(call)), ); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import json import asyncio async def get_weather_api(args: dict) -> dict: return {"temperature": 72, "condition": "sunny", "location": args["location"]} # replace with your weather API async def search_web_api(args: dict) -> dict: return {"results": [f"Result for: {args['query']}"]} # replace with your search API class ToolHandler: def __init__(self): self._tools = {} def register(self, name: str, func): self._tools[name] = func async def execute(self, tool_call) -> dict: func = self._tools.get(tool_call.function.name) if not func: raise ValueError(f"Unknown tool: {tool_call.function.name}") args = json.loads(tool_call.function.arguments) result = await func(args) return {"role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result)} handler = ToolHandler() handler.register("get_weather", get_weather_api) handler.register("search_web", search_web_api) async def execute_all_tools(message): return await asyncio.gather(*[handler.execute(call) for call in message.tool_calls]) ``` ### Parallel Tool Execution When the model returns multiple tool calls in one turn, execute them concurrently to reduce latency. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} async function getWeatherAsync(args: Record) { return { temperature: 72, condition: "sunny" }; } // replace with your weather API async function searchProductsAsync(args: Record) { return { products: [] as unknown[] }; } // replace with your product search async function checkInventoryAsync(args: Record) { return { inStock: true }; } // replace with your inventory API // Replace with your actual response and messages array const response = { choices: [{ message: { role: "assistant" as const, content: null as string | null, tool_calls: [] as Array<{ function: { name: string; arguments: string }; id: string }> } }] }; // e.g., client.chat.completions.create(...) let messages: Array<{ role: string; tool_call_id?: string; content: string | null }> = []; const dispatch: Record) => Promise> = { get_weather: getWeatherAsync, search_products: searchProductsAsync, check_inventory: checkInventoryAsync, }; async function executeToolsParallel( toolCalls: Array<{ function: { name: string; arguments: string }; id: string }>, ) { return Promise.all( toolCalls.map(async (call) => { const args = JSON.parse(call.function.arguments) as Record; const fn = dispatch[call.function.name] ?? (() => Promise.resolve({ error: `Unknown: ${call.function.name}` })); const result = await fn(args); return { role: "tool" as const, tool_call_id: call.id, content: JSON.stringify(result) }; }), ); } if (response.choices[0].message.tool_calls) { messages.push(response.choices[0].message); const toolResults = await executeToolsParallel(response.choices[0].message.tool_calls); messages = [...messages, ...toolResults]; } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import asyncio import json async def get_weather_async(args): return {"temperature": 72, "condition": "sunny"} # replace with your weather API async def search_products_async(args): return {"products": []} # replace with your product search async def check_inventory_async(args): return {"in_stock": True} # replace with your inventory API # Replace these with your actual API call and conversation history response = None # e.g. client.chat.completions.create(model=..., messages=messages, tools=[...]) messages: list = [] async def execute_tools_parallel(tool_calls): async def execute_single_tool(tool_call): function_name = tool_call.function.name arguments = json.loads(tool_call.function.arguments) # Route to appropriate function if function_name == "get_weather": result = await get_weather_async(arguments) elif function_name == "search_products": result = await search_products_async(arguments) elif function_name == "check_inventory": result = await check_inventory_async(arguments) else: result = {"error": f"Unknown function: {function_name}"} return { "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result) } # Execute all tools concurrently results = await asyncio.gather( *[execute_single_tool(call) for call in tool_calls] ) return results # Usage if response.choices[0].message.tool_calls: messages.append(response.choices[0].message) tool_results = await execute_tools_parallel( response.choices[0].message.tool_calls ) # Add to conversation messages.extend(tool_results) ``` ## Advanced Use Cases ### Database Integration Expose SQL access as a tool so the model can query data directly. Always sanitize queries before execution. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const sanitizeSql = (q: string) => q; // replace with your SQL sanitizer const getDbConnection = () => null as unknown as { execute: (q: string) => Promise<{ fetchAll: () => unknown[] }> }; // replace with your DB client const db = getDbConnection(); const tools = [ { type: "function" as const, function: { name: "query_database", description: "Query the customer database", parameters: { type: "object", properties: { query: { type: "string", description: "SQL query to execute" }, limit: { type: "integer", description: "Maximum number of results" }, }, required: ["query"], }, }, }, ]; async function queryDatabase(args: { query: string; limit?: number }) { const query = sanitizeSql(args.query); const limit = args.limit ?? 10; const results = await db.execute(`${query} LIMIT ${limit}`); // limit is integer-typed in the schema, safe to interpolate directly return { results: results.fetchAll() }; } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} sanitize_sql = lambda q: q # replace with your SQL sanitizer def get_db_connection(): pass # replace with your database client, e.g. from myapp.db import get_db_connection db = get_db_connection() tools = [ { "type": "function", "function": { "name": "query_database", "description": "Query the customer database", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "SQL query to execute" }, "limit": { "type": "integer", "description": "Maximum number of results" } }, "required": ["query"] } } } ] async def query_database(args): query = sanitize_sql(args["query"]) limit = args.get("limit", 10) results = await db.execute(f"{query} LIMIT {limit}") return {"results": results.fetchall()} ``` ### API Integration Expose external service actions (email, calendar, notifications) as tools the model can invoke during a conversation. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const apiTools = [ { type: "function", function: { name: "send_email", description: "Send an email to a recipient", parameters: { type: "object", properties: { to: { type: "string", description: "Email address" }, subject: { type: "string", description: "Email subject" }, body: { type: "string", description: "Email content" }, }, required: ["to", "subject", "body"], }, }, }, { type: "function", function: { name: "create_calendar_event", description: "Create a calendar event", parameters: { type: "object", properties: { title: { type: "string" }, start_time: { type: "string", format: "date-time" }, duration: { type: "integer", description: "Duration in minutes" }, attendees: { type: "array", items: { type: "string" } }, }, required: ["title", "start_time"], }, }, }, ]; const emailAPI = { send: async (args: Record) => ({ messageId: "msg_001" }) }; // replace with your email client const calendarAPI = { createEvent: async (args: Record) => ({ eventId: "evt_001" }) }; // replace with your calendar client const executeApiTool = async (toolCall) => { const { name } = toolCall.function; const args = JSON.parse(toolCall.function.arguments); switch (name) { case "send_email": return await emailAPI.send(args); case "create_calendar_event": return await calendarAPI.createEvent(args); default: throw new Error(`Unknown API tool: ${name}`); } }; ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import json # Replace with your actual API clients class EmailAPI: async def send(self, args: dict) -> dict: return {"message_id": "msg_001"} class CalendarAPI: async def create_event(self, args: dict) -> dict: return {"event_id": "evt_001"} email_api = EmailAPI() calendar_api = CalendarAPI() api_tools = [ { "type": "function", "function": { "name": "send_email", "description": "Send an email to a recipient", "parameters": { "type": "object", "properties": { "to": {"type": "string", "description": "Email address"}, "subject": {"type": "string", "description": "Email subject"}, "body": {"type": "string", "description": "Email content"}, }, "required": ["to", "subject", "body"], }, }, }, { "type": "function", "function": { "name": "create_calendar_event", "description": "Create a calendar event", "parameters": { "type": "object", "properties": { "title": {"type": "string"}, "start_time": {"type": "string", "format": "date-time"}, "duration": {"type": "integer", "description": "Duration in minutes"}, "attendees": {"type": "array", "items": {"type": "string"}}, }, "required": ["title", "start_time"], }, }, }, ] async def execute_api_tool(tool_call) -> dict: args = json.loads(tool_call.function.arguments) match tool_call.function.name: case "send_email": return await email_api.send(args) case "create_calendar_event": return await calendar_api.create_event(args) case _: raise ValueError(f"Unknown API tool: {tool_call.function.name}") ``` ### Multi-Step Workflows Run the model in a loop until it produces a final text response, enabling multi-step agent behavior without streaming. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import { OpenAI } from "openai"; import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); class WorkflowEngine { private tools: Record) => Promise; schema: ChatCompletionTool }> = {}; register(name: string, func: (args: Record) => Promise, schema: ChatCompletionTool) { this.tools[name] = { func, schema }; } async executeWorkflow(initialPrompt: string, maxSteps = 10): Promise { const conversation: ChatCompletionMessageParam[] = [{ role: "user", content: initialPrompt }]; for (let step = 0; step < maxSteps; step++) { const response = await client.chat.completions.create({ model: "openai/gpt-5.4", messages: conversation, tools: Object.values(this.tools).map((t) => t.schema), tool_choice: "auto", }); conversation.push(response.choices[0].message); if (!response.choices[0].message.tool_calls) { return response.choices[0].message.content ?? ""; } for (const call of response.choices[0].message.tool_calls) { conversation.push(await this.executeTool(call)); } } return "Workflow exceeded maximum steps"; } private async executeTool(toolCall: { function: { name: string; arguments: string }; id: string }): Promise { const args = JSON.parse(toolCall.function.arguments) as Record; const tool = this.tools[toolCall.function.name]; const result = tool ? await tool.func(args) : { error: `Unknown tool: ${toolCall.function.name}` }; return { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result) }; } } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import AsyncOpenAI import os import json client = AsyncOpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) class WorkflowEngine: def __init__(self): self.tools = {} self.conversation = [] def register_tool(self, name, func, schema): self.tools[name] = {"func": func, "schema": schema} async def execute_workflow(self, initial_prompt, max_steps=10): self.conversation = [{"role": "user", "content": initial_prompt}] for step in range(max_steps): response = await client.chat.completions.create( model="openai/gpt-5.4", messages=self.conversation, tools=[v["schema"] for v in self.tools.values()], tool_choice="auto" ) self.conversation.append(response.choices[0].message) # Check if tools need to be executed if response.choices[0].message.tool_calls: for tool_call in response.choices[0].message.tool_calls: result = await self.execute_tool(tool_call) self.conversation.append(result) else: # No tools called, workflow complete return response.choices[0].message.content return "Workflow exceeded maximum steps" async def execute_tool(self, tool_call): tool_name = tool_call.function.name arguments = json.loads(tool_call.function.arguments) if tool_name in self.tools: result = await self.tools[tool_name]["func"](arguments) else: result = {"error": f"Unknown tool: {tool_name}"} return { "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result) } ``` ## Error Handling Return structured error objects in tool outputs so the model can report failures or retry with corrected arguments. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} const validateArgs = (name: string, args: Record) => ({ valid: true, errors: [] as string[] }); // replace with your validator const executeFunction = async (name: string, args: Record): Promise => ({}); // replace with your dispatcher const safeToolExecution = async (toolCall) => { try { const args = JSON.parse(toolCall.function.arguments); // Validate arguments const validation = validateArgs(toolCall.function.name, args); if (!validation.valid) { return { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify({ error: "Invalid arguments", details: validation.errors, }), }; } // Execute with timeout const result = await Promise.race([ executeFunction(toolCall.function.name, args), new Promise((_, reject) => setTimeout(() => reject(new Error("Tool execution timeout")), 30000), ), ]); return { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result), }; } catch (error) { console.error(`Tool execution failed: ${error.message}`); return { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify({ error: "Tool execution failed", message: error.message, }), }; } }; ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import json import asyncio def validate_args(name: str, args: dict) -> dict: return {"valid": True, "errors": []} # replace with your validator async def execute_function(name: str, args: dict): return {} # replace with your dispatcher async def safe_tool_execution(tool_call) -> dict: try: args = json.loads(tool_call.function.arguments) validation = validate_args(tool_call.function.name, args) if not validation["valid"]: return { "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps({"error": "Invalid arguments", "details": validation["errors"]}), } result = await asyncio.wait_for( execute_function(tool_call.function.name, args), timeout=30, ) return {"role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result)} except Exception as e: return { "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps({"error": "Tool execution failed", "message": str(e)}), } ``` ## Best Practices ### Tool design * Use clear, descriptive function names. * Provide detailed parameter descriptions. * Include examples in descriptions. * Make tools idempotent when possible. ### Schema design ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "object", "properties": { "location": { "type": "string", "description": "City and state (e.g., 'San Francisco, CA')", "examples": ["New York, NY", "London, UK"] }, "units": { "type": "string", "enum": ["metric", "imperial"], "description": "Temperature unit system", "default": "metric" } }, "required": ["location"] } ``` ### Security considerations * Never expose destructive operations directly. * Validate all inputs thoroughly. * Use allowlists for sensitive operations. * Implement proper authentication. * Log all tool executions. ## Troubleshooting **Tool not being called** * Check tool descriptions are clear. * Verify parameter schemas are correct. * Ensure tool\_choice is set appropriately. * Try more explicit prompts. **Invalid arguments** * Validate JSON Schema thoroughly. * Add parameter examples. * Check required fields are marked. * Simplify complex parameter structures. **Execution failures** * Implement proper error handling. * Add timeout protection. * Validate inputs before execution. * Return structured error messages. ## Limitations | Limitation | Impact | Workaround | | --------------------- | ---------------------------- | ---------------------------- | | **Tool limit** | Max \~20 tools per request | Group related functions | | **Parameter size** | Large schemas may fail | Simplify parameter structure | | **Execution time** | Tools block response | Use async patterns | | **Error propagation** | Failures can break workflow | Implement error recovery | | **Model differences** | Varying tool calling quality | Test across models | ## Performance Optimization ### Tool caching Cache results from read-only tools to avoid redundant external calls within a session. ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} class CachedToolExecutor { constructor() { this.cache = new Map(); this.cacheTTL = 300000; // 5 minutes } getCacheKey(toolCall) { return `${toolCall.function.name}:${toolCall.function.arguments}`; } async executeFunction(toolCall) { throw new Error("executeFunction must be implemented in a subclass"); } async execute(toolCall) { const key = this.getCacheKey(toolCall); const cached = this.cache.get(key); if (cached && Date.now() - cached.timestamp < this.cacheTTL) { return cached.result; } const result = await this.executeFunction(toolCall); this.cache.set(key, { result, timestamp: Date.now() }); return result; } } ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import time import json class CachedToolExecutor: def __init__(self, cache_ttl: int = 300): self._cache: dict = {} self._cache_ttl = cache_ttl def _cache_key(self, tool_call) -> str: return f"{tool_call.function.name}:{tool_call.function.arguments}" async def execute_function(self, tool_call) -> dict: raise NotImplementedError("Implement in subclass") async def execute(self, tool_call) -> dict: key = self._cache_key(tool_call) entry = self._cache.get(key) if entry and (time.time() - entry["timestamp"]) < self._cache_ttl: return entry["result"] result = await self.execute_function(tool_call) self._cache[key] = {"result": result, "timestamp": time.time()} return result ``` ### Batch operations Accept arrays of inputs in a single tool to reduce the number of model turns required for bulk operations. ```typescript TypeScript (Responses) theme={"theme":{"light":"github-light","dark":"github-dark"}} const getWeather = (location: string) => ({ temperature: 72, condition: "sunny", location }); // replace with your weather API const getWeatherBatch = (locations: string[]) => Object.fromEntries(locations.map((loc) => [loc, getWeather(loc)])); const getWeatherBatchTool = { type: "function" as const, name: "get_weather_batch", description: "Get weather for multiple locations in a single call", parameters: { type: "object", properties: { locations: { type: "array", items: { type: "string" }, description: "List of city names" }, }, required: ["locations"], }, }; ``` ```python Python (Responses) theme={"theme":{"light":"github-light","dark":"github-dark"}} # Pattern fragment: add client setup and get_weather stub before using # Instead of multiple individual calls def get_weather_batch(locations): return {loc: get_weather(loc) for loc in locations} # Tool that accepts multiple inputs get_weather_batch_tool = { "type": "function", "name": "get_weather_batch", "description": "Get weather for multiple locations in a single call", "parameters": { "type": "object", "properties": { "locations": { "type": "array", "items": {"type": "string"} } }, "required": ["locations"] } } ``` # Managed Prompts in router requests Source: https://docs.orq.ai/docs/ai-gateway/features/using-prompts Reference managed Prompts by ID in Chat Completions requests to update prompt content without a code deploy. **Use Cases** * Updating copy or instructions without a code deploy. * Sharing a single prompt definition across multiple services. * Letting non-engineers iterate on prompts through the UI without touching code. *** ## Overview The `orq.prompt` parameter references a [Prompt](/docs/ai-studio/prompts/prompts) created in **Orq.ai** instead of hardcoding its messages in application code. The referenced prompt's messages are prepended to the request `messages` before the model is called. `orq.prompt` is supported on the Chat Completions endpoint `POST https://my.orq.ai/v2/router/chat/completions`. Only the `latest` version of a prompt can be referenced. ## Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [ { "role": "user", "content": "How do I reset my password?" } ], "orq": { "prompt": { "id": "prompt_01ARZ3NDEKTSV4RRFFQ69G5FAV", "version": "latest" } } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v2/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: "How do I reset my password?", }, ], orq: { prompt: { id: "prompt_01ARZ3NDEKTSV4RRFFQ69G5FAV", version: "latest", }, }, }); console.log(response.choices[0].message.content); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v2/router", ) response = client.chat.completions.create( model="openai/gpt-5.6-sol", messages=[ { "role": "user", "content": "How do I reset my password?", } ], extra_body={ "orq": { "prompt": { "id": "prompt_01ARZ3NDEKTSV4RRFFQ69G5FAV", "version": "latest", } } }, ) print(response.choices[0].message.content) ``` **Prerequisites**: the Prompt must be created in **Orq.ai** before use. Copy its ID from the Prompts page. ## Configuration | Parameter | Type | Required | Description | | --------- | ---------- | -------- | ---------------------------------------------------------- | | `id` | string | Yes | Unique identifier of the Prompt in **Orq.ai** | | `version` | `"latest"` | Yes | Version of the prompt to use. Only `"latest"` is supported | ## How it works 1. The router resolves the referenced Prompt at request time. 2. The prompt's messages are inserted at the start of the request `messages`. 3. When the Prompt has a model configured, that model replaces the request `model`. 4. When the prompt ID cannot be resolved, the request runs unchanged with the original `messages` and `model`. No error is returned. Because resolution happens at request time, saving a change to the Prompt in **Orq.ai** takes effect on the next request without redeploying the application. ## Troubleshooting **Prompt content missing from responses** A request with an unresolvable prompt reference does not fail; it runs without the prompt's messages. Check the following: * Verify the prompt ID is correct. * Check the Prompt exists in the workspace the [API Key](/docs/ai-studio/organization/api-keys) belongs to. * Verify the Prompt was saved after the last edit. **Unexpected model used** A model configured on the Prompt overrides the `model` set in the request. Remove the model from the Prompt configuration to keep the request's model. ## Limitations | Limitation | Impact | Workaround | | --------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------- | | Latest version only | `version` accepts only `"latest"`; a specific version cannot be pinned | None. Saved prompt changes apply from the next request | | Chat Completions only | `/v3/router` endpoints ignore `orq.prompt` without an error | Use `POST /v2/router/chat/completions` | | Pre-creation required | A request referencing a missing Prompt runs without its messages | Create and save the Prompt in **Orq.ai** before referencing it | | Workspace scoped | Prompts cannot be referenced across workspaces | Create the Prompt in each workspace that needs it | # Web search in Responses API Source: https://docs.orq.ai/docs/ai-gateway/features/web-search Give models access to current web information via the Responses API with built-in web search across OpenAI, Anthropic, and Google. **Use Cases** * Answering questions about events beyond the model's training cutoff. * Grounding responses in live data (pricing, availability, breaking news). * Research assistants that need to cite verifiable, up-to-date sources. * Customer support bots that look up current documentation or policies at query time. *** ## Overview Use **`POST /responses`** on the AI Gateway with the same request shape as the [OpenAI Responses API](https://developers.openai.com/api/reference/responses/overview): `model`, `input`, and a `tools` array that includes a built-in web search entry. The **AI Gateway** forwards search to each provider’s native capability when the model supports it. See [Responses API](/docs/ai-gateway/features/responses-api) for an overview of the endpoint and when to choose it over Chat Completions. There is no separate Perplexity or Parallel tool in the **AI Gateway**; the supported contract is **OpenAI-style** `web_search` / `web_search_preview` tools on `/responses`, mapped to Anthropic and Google where applicable. Prefer models whose metadata indicates web search support. See [Supported Models](/docs/ai-gateway/supported-models) and the workspace model list. ## Quick start Use the OpenAI SDK against the AI Gateway base URL and call the Responses API directly. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://my.orq.ai/v3/router/responses" \ -H "Authorization: Bearer ${ORQ_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "What are the latest changes to the OpenAI Responses API?", "tools": [{ "type": "web_search_preview" }] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "What are the latest changes to the OpenAI Responses API?", tools: [{ type: "web_search_preview" }], }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.6-sol", input="What are the latest changes to the OpenAI Responses API?", tools=[{"type": "web_search_preview"}], ) print(response.output_text) ``` Endpoint: `POST https://my.orq.ai/v3/router/responses` ## Tool definitions Request `tools` entries use the discriminated **`type`** field, matching the gateway schema. ### `web_search_preview` | Field | Type | Description | | --------------------- | ---------------------- | ------------------------------------------------------------------------------------------- | | `type` | `"web_search_preview"` | Identifies the preview web search tool. | | `domains` | string\[] | Optional allowlist of domains to restrict search. | | `search_context_size` | string | Optional. One of `low`, `medium`, `high`. Controls how much context to retrieve per result. | | `user_location` | object | Optional. Hints for localized results (`type`, `city`, `country`, `region`, `timezone`). | ### `web_search` | Field | Type | Description | | --------------------- | -------------- | ---------------------------------------------------------------------- | | `type` | `"web_search"` | Stable web search tool type. | | `search_context_size` | string | Optional. `low`, `medium`, `high`. | | `user_location` | object | Optional. Same shape as above. | | `filters` | object | Optional. e.g. `allowed_domains` (nullable array) to restrict domains. | The **AI Gateway** accepts the full tool object on `/responses`. **OpenAI** requests pass tools through to the upstream Responses API. **Anthropic** and **Google** paths map `web_search` / `web_search_preview` to native web search tools; fields the upstream API does not support may be ignored. ## `include` (optional) You can ask for additional fields on web search output items, aligned with OpenAI Responses `include`: | Value | Meaning | | -------------------------------- | ------------------------------------------------ | | `web_search_call.results` | Include detailed search results where available. | | `web_search_call.action.sources` | Include source URLs and titles on the action. | Example request body: ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "model": "openai/gpt-5.6-sol", "input": "Summarize today’s headlines in finance.", "tools": [{ "type": "web_search_preview" }], "include": ["web_search_call.action.sources"] } ``` ## Output: `web_search_call` Responses can contain output items with `"type": "web_search_call"`: | Field | Description | | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `id` | Item id. | | `type` | `"web_search_call"`. | | `status` | Lifecycle status for the call. | | `action` | Optional. `type` may be `search`, `open_page`, or `find`, plus fields like `query`, `url`, `pattern`, and `sources` (url + title). | Exact payload shape matches what the provider returns; streaming uses the same event model as other `/responses` streams. ## Provider behavior ### OpenAI Tools and `include` are passed to **`client.responses.create`** with the model id (for example `openai/gpt-5.6-sol`). Use the same parameters OpenAI documents for [web search](https://platform.openai.com/docs/guides/tools-web-search) on the Responses API. Set up your OpenAI API key to use GPT-5.6 Sol with web search. ### Anthropic `web_search` and `web_search_preview` in `tools` are mapped to Anthropic’s web search tool for the Messages path used under `/responses`. Set up your Anthropic API key to use Claude with web search. ### Google Gemini The same tool types enable **Google Search grounding** (`googleSearch`) for Gemini models that support it. Set up your Google AI API key to use Gemini models with Google Search grounding. ## `tool_choice` You can steer the model toward web search using the structured `tool_choice` form, for example: ```json JSON theme={"theme":{"light":"github-light","dark":"github-dark"}} { "tool_choice": { "type": "web_search_preview" } } ``` Supported `tool_choice.type` values for built-in tools are defined in the API schema (including `web_search`, `web_search_preview`, and related literals). Strings `none`, `auto`, and `required` behave as usual. ## Streaming Set **`"stream": true`** on the same JSON body. The server responds with **`text/event-stream`** and events in the OpenAI-style response stream format. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -N -X POST "https://my.orq.ai/v3/router/responses" \ -H "Authorization: Bearer ${ORQ_API_KEY}" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "What is the current phase of the moon for San Francisco?", "tools": [{ "type": "web_search_preview" }], "stream": true }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const stream = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "What is the current phase of the moon for San Francisco?", tools: [{ type: "web_search_preview" }], stream: true, }); for await (const event of stream) { console.log(event); } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) stream = client.responses.create( model="openai/gpt-5.6-sol", input="What is the current phase of the moon for San Francisco?", tools=[{"type": "web_search_preview"}], stream=True, ) for event in stream: print(event) ``` ## Code examples Set **`ORQ_API_KEY`** (or pass the key explicitly) so the `Authorization` header resolves. ### Non-streaming ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://my.orq.ai/v3/router/responses" \ -H "Authorization: Bearer ${ORQ_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "What is the current phase of the moon for San Francisco?", "tools": [{ "type": "web_search_preview" }], "include": ["web_search_call.action.sources"] }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "What is the current phase of the moon for San Francisco?", tools: [{ type: "web_search_preview" }], include: ["web_search_call.action.sources"], }); console.log(response.output_text); for (const item of response.output ?? []) { if (item.type === "web_search_call") { console.log("web_search_call", item.action); } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.6-sol", input="What is the current phase of the moon for San Francisco?", tools=[{"type": "web_search_preview"}], include=["web_search_call.action.sources"], ) print(response.output_text) for item in response.output or []: if item.type == "web_search_call": print("web_search_call", item.action) ``` ### Streaming ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -N -X POST "https://my.orq.ai/v3/router/responses" \ -H "Authorization: Bearer ${ORQ_API_KEY}" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "What is the current phase of the moon for San Francisco?", "tools": [{ "type": "web_search_preview" }], "stream": true }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const stream = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "What is the current phase of the moon for San Francisco?", tools: [{ type: "web_search_preview" }], stream: true, }); for await (const event of stream) { console.log(event); } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) stream = client.responses.create( model="openai/gpt-5.6-sol", input="What is the current phase of the moon for San Francisco?", tools=[{"type": "web_search_preview"}], stream=True, ) for event in stream: print(event) ``` For cURL streaming, `-N` disables buffering so SSE lines show up as they arrive. Parse `data:` lines from the response body the same way you would against OpenAI’s Responses stream. ## See also * [Responses API](/docs/ai-gateway/features/responses-api): the endpoint web search runs on, and when to choose it over Chat Completions * [OpenAI-Compatible API](/docs/ai-gateway/features/openai-compatible-api): base URL and `/responses` * [Reasoning](/docs/ai-gateway/features/reasoning): `reasoning` for OpenAI models via Responses API; `thinking` for Anthropic and Google Gemini via Chat Completions * [Tool Calling](/docs/ai-gateway/features/tool-calling): function tools on `/chat/completions` and `/responses` # AI Gateway Source: https://docs.orq.ai/docs/ai-gateway/get-started/introduction A standalone routing layer for production LLM traffic. Reach 500+ models through one OpenAI-compatible endpoint with fallbacks and usage tracking. AI Gateway is a standalone routing layer for production LLM traffic. Send requests through one OpenAI-compatible endpoint to access 500+ models across providers, with built-in fallbacks, retries, load balancing, and usage tracking. ## Quick Start Complete the first request in a few minutes. [Sign up](https://my.orq.ai/auth/signup), then open the AI Gateway workspace. Open BYOK, choose [OpenAI](/docs/ai-studio/integrations/providers/openai), select Connect, and add your provider API key. Open API Keys, select Create API key, and copy the key. Set the key in your terminal and send a request to the Responses endpoint. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export ORQ_API_KEY="your-api-key" curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4", "input": "Hello, world!" }' ``` Self-hosted and on-premise deployments serve `/v3/router` under their own hostname. See [Base URLs](/reference/base-urls). Open [Traces](/docs/ai-gateway/traces) to review the response, latency, token usage, and cost. ## Next steps Keep requests flowing when a provider fails. Browse the model catalog and provider capabilities. Move an existing application from the OpenAI SDK, OpenRouter, or LiteLLM. Answers to common questions about models and their capabilities. # Migrate from OpenAI, OpenRouter, LiteLLM Source: https://docs.orq.ai/docs/ai-gateway/get-started/migrate Move existing LLM traffic to the Orq.ai AI Gateway from the OpenAI SDK, OpenRouter, or LiteLLM by changing the base URL, the API key, and the model name. The **AI Gateway** uses the OpenAI API format. Migrating an existing application means changing three values: the base URL, the API key, and the model name. Request bodies, response bodies, and streaming stay the same. Tool calling is unchanged for most models, with one exception noted below. ## What changes | Item | Change to | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Base URL | `https://my.orq.ai/v3/router`, the same for every organization, workspace, and project | | Authorization header | `Bearer $ORQ_API_KEY` | | Model name | `provider/model`, for example `openai/gpt-5.6-sol`. Always include the prefix: some models are served by more than one provider, and the prefix selects between them | The base URL does not vary by region or by workspace. Data stored in the **Orq.ai** platform resides in the European Union, while the region a model is served from depends on the model and is filterable on the **Models** page. See [Sovereign AI](/docs/enterprise/sovereign-ai). A private model carries its workspace in the model name as `@/` rather than in the URL. See [Private Models](/docs/ai-gateway/private-models). ## What works immediately Once those three values are in place, the following need no further code: * **Cost per call**: every request records input, output, and total cost. * **Traces**: every request is recorded with its latency, token counts, and the model that answered. * **Model access**: every model enabled on the [Models page](/docs/ai-gateway/using-the-router) is reachable from the same client, whichever provider serves it. Browse the catalog in [Supported Models](/docs/ai-gateway/supported-models). * **Routing Rules**: route or restrict traffic from the **AI Gateway** interface, without touching the application. See [Routing Rules](/docs/ai-gateway/configuration/routing-rules). **Fallbacks**, **Retries**, **Cache**, and **Guardrails** are opt-in fields on the request body, covered in [What Orq.ai adds](#what-orq-ai-adds). ## What does not carry over Two areas do not transfer. | Area | Detail | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Assistants API and fine-tuning | Not available. `/assistants` and `/fine_tuning/jobs` return `404`. Applications built on those endpoints cannot migrate as they are. See [OpenAI-Compatible API](/docs/ai-gateway/features/openai-compatible-api) for the supported endpoint list. | | Tool calling with OpenAI reasoning models | Sending `tools` to `/chat/completions` with a reasoning model such as `openai/gpt-5.6-sol` returns `400`. Use `/responses` instead. Setting `reasoning_effort` to `none` also unblocks `/chat/completions`, at the cost of the reasoning the model would otherwise do. Other models, including `openai/gpt-4o-mini` and `anthropic/claude-sonnet-5`, accept `tools` on `/chat/completions` normally. | ## Before starting Complete the [Quick Start](/docs/ai-gateway/get-started/introduction) first. A migration needs an API key, and every model the application calls has to be enabled on the [Models page](/docs/ai-gateway/using-the-router), not just the one used for the first request. Access to those models comes from [**Credits**](/docs/ai-studio/organization/credits) or [**BYOK**](/docs/ai-gateway/providers-overview). Export the key before starting. The steps below read it, and so does the coding agent when it checks model names against the catalog. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export ORQ_API_KEY="your-api-key" ``` ## Migrate with a coding agent Paste the prompt below into a coding agent such as **Claude Code**, **Cursor**, or **Codex**. The agent finds the LLM calls in the repository and rewrites them. The prompt does the following: 1. Points every LLM client at `https://my.orq.ai/v3/router`. 2. Replaces the old API key variable with `ORQ_API_KEY`, in code and in environment files. 3. Rewrites model names to the `provider/model` format. 4. Converts routing settings from the previous gateway instead of deleting them. 5. Checks every model name against the catalog, and leaves a call site untouched rather than substituting a model or half-migrating it. 6. Leaves request and response handling untouched. Start from a clean git working tree, so every change is visible in `git diff` and revertible with `git checkout .`. Review the diff before running the application. Migrating by hand instead? Skip to [Migrate from the OpenAI SDK](#migrate-from-the-openai-sdk). ```text Migration prompt theme={"theme":{"light":"github-light","dark":"github-dark"}} Migrate this project to the orq.ai AI Gateway. Make the smallest change that works. Do not restructure the application. Step 1. Find every place the project calls an LLM. Check source files, configuration files, environment files, and container definitions. Step 2. Identify the current setup, then apply the matching rule: - OpenAI SDK with no custom base URL: add the base URL below and change the API key variable. - OpenRouter (base URL contains openrouter.ai): replace the base URL and the API key variable, then convert the OpenRouter settings using Step 3. - LiteLLM proxy (base URL points at a self-hosted proxy): replace the base URL and the API key variable. Replace each model alias with the full provider/model name, read from litellm_params.model in the LiteLLM config file. - LiteLLM Python library used directly: keep the library and set api_base to the base URL below, and prefix each model with openai/, giving openai//. LiteLLM strips that openai/ prefix before forwarding, so the gateway receives /. Do not replace the library. Step 3. Convert OpenRouter settings. Do not delete them silently, because they carry routing behavior: - models array with route: "fallback" becomes fallbacks: [{"model": "..."}]. The first entry of the models array repeats the top-level model field, so leave it out and list only the entries after it. Including it makes the request fall back to the model that just failed. - provider preferences: remove every sub-option from the request and report each one by name. There is no drop-in request-body equivalent. Choosing between models per request maps to the load_balancer field, and organization-wide routing maps to Routing Rules in the orq.ai interface. Do not write either one, because both need a human decision. - plugins: remove the field and report every entry by name. orq.ai uses the same field name with different accepted id values, and an unrecognized id makes the whole request fail with a 400, so keeping the array breaks the call site. Do not write a replacement, because the equivalent needs a human decision. - debug: remove. - Headers HTTP-Referer, X-Title, X-OpenRouter-Title, X-OpenRouter-Categories: remove and report each one. Do not write a replacement. The application name maps to the name field, which a human can add later. Step 4. Apply these values: - Base URL: https://my.orq.ai/v3/router - Auth: Authorization: Bearer $ORQ_API_KEY - Environment variable: ORQ_API_KEY. Do not add fallback variables. Rename the variable in code, in committed templates such as .env.example, and in container definitions. In templates, leave the value empty. Remove an old provider or gateway variable only when nothing in this repository still reads it, and list every variable removed. Never read, print, or copy a secret value. If a file holds real secrets, report its path instead of editing it. - Model format: provider/model, for example openai/gpt-5.6-sol or anthropic/claude-sonnet-5. Step 5. Model names. Never substitute a different model version, because cost and behavior differ. A newer version is not an equivalent, and a similar name carrying a different identifier is not the same model. Check every name against the catalog: curl https://my.orq.ai/v3/router/models -H "Authorization: Bearer $ORQ_API_KEY" Match names exactly. A punctuation difference or an added date suffix makes it a different model. At a LiteLLM Python library call site, look up only the part after the openai/ prefix, because that is the name the gateway receives. Take the provider segment from the code where it is stated: a client configured against one specific provider, or a config entry that names it. Where the code does not state it, a provider may be proposed, but only keep the result if that exact provider/model appears in the catalog, and report the inference. Never keep a provider that the catalog does not confirm. Flag a model instead of changing it when the name is absent from the catalog, when the catalog cannot be reached, or when no candidate provider/model can be confirmed against the catalog. Step 6. Leave every call site that has a flagged model completely unchanged, including its base URL, its API key variable, and any environment variable only that call site still uses. Migrating the rest of such a call site produces a request that fails at runtime. Report each one as blocked on a decision. This overrides Step 3: report the settings on a blocked call site instead of converting them. Where one shared client object serves both migrated and blocked calls, leave that client as it is and add a separate client for the migrated calls, rather than repointing the one the blocked call still depends on. Step 7. Remove configuration that existed only to run the previous gateway, such as a LiteLLM config file and its proxy service definition. Keep anything a call site blocked in Step 6 still needs. List every file removed. Step 8. Do not change message content, response handling, streaming logic, or tool-calling logic. The API format is identical. The only body changes permitted are the field conversions in Step 3. Step 9. Report three lists: - model names changed, as old name and new name - model names flagged for a human decision, and the call sites left unmigrated because of them - settings that moved to the orq.ai interface instead of code Show the full diff. Do not commit. ``` Model names are not guaranteed to match between gateways. Check every name the agent reports against [Supported Models](/docs/ai-gateway/supported-models) before running the application in production. A `404` means either the name is wrong or the model is not enabled on the [Models page](/docs/ai-gateway/using-the-router). `fallbacks` does not cover this case, because the model is resolved before routing runs. ## Migrate from the OpenAI SDK Change the base URL and the API key. Add the provider prefix to the model name. Both `/chat/completions` and `/responses` are available, so keep whichever the application already uses. **Before** ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("OPENAI_API_KEY"), ) response = client.responses.create( model="gpt-5.6-sol", input="Hello!", ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); const response = await client.responses.create({ model: "gpt-5.6-sol", input: "Hello!", }); ``` **After** ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.environ.get("ORQ_API_KEY"), ) response = client.responses.create( model="openai/gpt-5.6-sol", input="Hello!", ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://my.orq.ai/v3/router", apiKey: process.env.ORQ_API_KEY, }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Hello!", }); ``` Models from every other provider now work through the same client. See [Supported Models](/docs/ai-gateway/supported-models). ## Migrate from OpenRouter **OpenRouter** and the **AI Gateway** both use the `provider/model` naming format, so most model names stay the same. The catalogs differ, so confirm every name against `GET /models` or the **Models** page before switching. **Before** ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=os.environ.get("OPENROUTER_API_KEY"), ) response = client.chat.completions.create( model="openai/gpt-4o-mini", messages=[{"role": "user", "content": "Hello!"}], ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://openrouter.ai/api/v1", apiKey: process.env.OPENROUTER_API_KEY, }); const response = await client.chat.completions.create({ model: "openai/gpt-4o-mini", messages: [{ role: "user", content: "Hello!" }], }); ``` **After** ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.environ.get("ORQ_API_KEY"), ) response = client.chat.completions.create( model="openai/gpt-4o-mini", messages=[{"role": "user", "content": "Hello!"}], ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://my.orq.ai/v3/router", apiKey: process.env.ORQ_API_KEY, }); const response = await client.chat.completions.create({ model: "openai/gpt-4o-mini", messages: [{ role: "user", content: "Hello!" }], }); ``` ### Move OpenRouter settings across Most old **OpenRouter** fields are ignored rather than rejected, so a partial migration does not fail. The routing they configured no longer applies. Move each one deliberately. `plugins` is the exception. Both gateways use that field name, and the **AI Gateway** reads it. A leftover **OpenRouter** `plugins` array fails the whole call with a generic `Invalid request body` that does not name the field. | OpenRouter setting | **Orq.ai** equivalent | | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `models` array with `route: "fallback"` | [`fallbacks`](/docs/ai-gateway/features/retries#fallbacks), listing the entries after the first. The first repeats `model` | | `provider.order`, `provider.only`, `provider.ignore` | [`load_balancer`](/docs/ai-gateway/features/load-balancing) to choose between models per request, or [Routing Rules](/docs/ai-gateway/configuration/routing-rules) to restrict routing for the whole organization | | `provider.allow_fallbacks` | [`fallbacks`](/docs/ai-gateway/features/retries#fallbacks), which is explicit rather than automatic | | `provider.data_collection`, `provider.zdr` | Zero data retention is a property of the model. Filter for it on the **Models** page. See [Sovereign AI](/docs/enterprise/sovereign-ai) | | `provider.sort` | [`load_balancer`](/docs/ai-gateway/features/load-balancing) with latency-based selection | | `plugins` array | [`plugins`](/docs/ai-gateway/features/plugins/overview): same field name, different accepted `id` values. Remove the **OpenRouter** array rather than leaving it, because an unrecognized `id` returns `400` | | `HTTP-Referer`, `X-Title`, `X-OpenRouter-Title`, `X-OpenRouter-Categories` headers | [`name`](/docs/ai-gateway/app-tracking) for the application name, [`tags`](/docs/ai-studio/observability/span-attributes#custom-metadata-and-attribution) for filterable labels | The remaining `provider` sub-options have no per-request equivalent: `require_parameters`, `quantizations`, `enforce_distillable_text`, `preferred_min_throughput`, `preferred_max_latency`, and `max_price`. Applications that depend on any of these need their routing decided ahead of the request, by selecting models explicitly or through [Routing Rules](/docs/ai-gateway/configuration/routing-rules). ## Migrate from LiteLLM **LiteLLM Proxy** is a server run inside the organization's own infrastructure. Applications call it instead of calling providers directly, and it forwards each request to the real provider. It exposes an OpenAI-compatible endpoint, on port 4000 by default. There are two ways to move off it. Pick one before making any change. | Option | What happens | Tradeoff | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Retire the proxy** (covered below) | The application calls the **AI Gateway** directly and the **LiteLLM** server is shut down | Every model name in the application has to be rewritten | | **Keep the proxy behind Orq.ai** | The **LiteLLM** instance is connected as a provider and its models are imported. Each name keeps its nickname, prefixed as `@litellm/`, so no real model has to be looked up | The **LiteLLM** server stays in the stack, and every model name still needs the prefix added. See [LiteLLM custom model provider](/docs/ai-studio/integrations/providers/litellm) | The rest of this section covers the first option. ### Find the real model name **LiteLLM Proxy** does not use provider model names. It uses nicknames defined in its `config.yaml`. Each entry pairs the nickname the application calls with the real model behind it: ```yaml config.yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} model_list: - model_name: fast-model # the nickname the application sends litellm_params: model: openai/gpt-5.6-sol # the real model it forwards to ``` The application above sends `fast-model`. The **AI Gateway** has no such name, so replace it with the real model, `openai/gpt-5.6-sol`. Open `config.yaml` and read the `litellm_params.model` value for every nickname the application uses. A nickname can look exactly like a real model name. `model_name: gpt-4o` can point at `litellm_params.model: azure/gpt-4o-eu`, which is a different model on a different provider. Read `litellm_params.model` for every entry, including the ones that already look correct. ### Change the client **Before**, pointing at the proxy and calling it by its nickname: ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="http://localhost:4000", api_key=os.environ.get("LITELLM_API_KEY"), ) response = client.chat.completions.create( model="fast-model", messages=[{"role": "user", "content": "Hello!"}], ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "http://localhost:4000", apiKey: process.env.LITELLM_API_KEY, }); const response = await client.chat.completions.create({ model: "fast-model", messages: [{ role: "user", content: "Hello!" }], }); ``` **After**, pointing at the **AI Gateway** and calling the real model: ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.environ.get("ORQ_API_KEY"), ) response = client.chat.completions.create( model="openai/gpt-5.6-sol", messages=[{"role": "user", "content": "Hello!"}], ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://my.orq.ai/v3/router", apiKey: process.env.ORQ_API_KEY, }); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Hello!" }], }); ``` Routing behavior set in `config.yaml`, such as fallbacks and retries, moves to the request body. See [What Orq.ai adds](#what-orq-ai-adds). To keep **LiteLLM** as the caller and send its traces to **Orq.ai** instead, see [LiteLLM observability](/docs/ai-gateway/integrations/frameworks/litellm). ## Verify the migration ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Confirm the migration works." }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.environ.get("ORQ_API_KEY"), ) response = client.responses.create( model="openai/gpt-5.6-sol", input="Confirm the migration works.", ) print(response.output_text) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://my.orq.ai/v3/router", apiKey: process.env.ORQ_API_KEY, }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Confirm the migration works.", }); console.log(response.output_text); ``` A `200` response confirms that the base URL, the API key, and the model name are correct. For an application that uses chat completions, replace `/responses` with `/chat/completions` and send `messages` instead of `input`. Open [**Traces**](/docs/ai-gateway/traces) in the **AI Gateway** and open the newest request. Confirm all four: | Check | Expected | If it does not match | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Total cost | Greater than zero | The model has no pricing attached, or the provider connection is missing. Check the model on the [Models page](/docs/ai-gateway/using-the-router) | | Model that answered | The same model that was requested, shown without the `provider/` prefix. Requesting `openai/gpt-5.6-sol` reports `gpt-5.6-sol` | A different model means a fallback or a [Routing Rule](/docs/ai-gateway/configuration/routing-rules) redirected the call | | Input and output tokens | Both greater than zero | The request or the response was empty | | Status | Completed, no error | Inspect the provider error on the trace | If no request appears at all, the application is still calling the old endpoint. Search the repository for the previous base URL and the previous key variable. Repeat for every service that calls an LLM. Filter [**Traces**](/docs/ai-gateway/traces) by model to confirm that every model the application uses now appears, and to find traffic that has not moved yet. ## What Orq.ai adds Once traffic is flowing, these optional fields go on the same request body. | Field | What it does | Reference | | --------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `fallbacks` | Tries another model when the first one fails | [Fallbacks](/docs/ai-gateway/features/retries#fallbacks) | | `retry` | Repeats a failed request, with a configurable count and error codes | [Retries](/docs/ai-gateway/features/retries) | | `timeout` | Stops waiting for a provider after a set number of milliseconds | [Timeouts](/docs/ai-gateway/features/timeouts) | | `load_balancer` | Splits traffic across several models by weight or latency | [Load Balancing](/docs/ai-gateway/features/load-balancing) | | `cache` | Returns a stored answer for a repeated identical request | [Cache](/docs/ai-gateway/features/cache) | | `guardrails` | Checks the input or the output, and blocks the request when a check fails | [Guardrails](/docs/ai-gateway/configuration/guardrails) | | `plugins` | Removes personal data before the provider sees it, or repairs malformed JSON | [Plugins](/docs/ai-gateway/features/plugins/overview) | | `security` | Masks request and response data in stored traces | [Security](/docs/ai-gateway/features/security) | | `name` | Names the calling application on the trace | [App Tracking](/docs/ai-gateway/app-tracking) | | `tags` | Labels a request so it can be filtered later | [Metadata](/docs/ai-studio/observability/span-attributes#custom-metadata-and-attribution) | | `thread` | Groups the turns of one conversation | [Threads](/docs/ai-gateway/thread-management) | | `identity` | Assigns a request, and its cost, to one end user | [Identities](/docs/ai-studio/observability/identities) | Set in the **Orq.ai** interface rather than the request body: [Budgets](/docs/ai-gateway/budgets) for spend limits and alerts, [Private Models](/docs/ai-gateway/private-models) for self-hosted or fine-tuned models, and [Sovereign AI](/docs/enterprise/sovereign-ai) for data residency and zero-retention providers. Adding a fallback and a cache to an existing call: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [{ "role": "user", "content": "Hello!" }], "fallbacks": [{ "model": "anthropic/claude-sonnet-5" }], "cache": { "type": "exact_match", "ttl": 3600 } }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.environ.get("ORQ_API_KEY"), ) response = client.chat.completions.create( model="openai/gpt-5.6-sol", messages=[{"role": "user", "content": "Hello!"}], extra_body={ "fallbacks": [{"model": "anthropic/claude-sonnet-5"}], "cache": {"type": "exact_match", "ttl": 3600}, }, ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://my.orq.ai/v3/router", apiKey: process.env.ORQ_API_KEY, }); const orqFields = { fallbacks: [{ model: "anthropic/claude-sonnet-5" }], cache: { type: "exact_match", ttl: 3600 }, }; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Hello!" }], ...orqFields, }); ``` ## Next steps Keep requests flowing when a provider fails. Browse the model catalog and provider capabilities. Review the full list of compatible endpoints. Inspect cost, latency, and token usage per request. # Continue Source: https://docs.orq.ai/docs/ai-gateway/integrations/code-assistants/continue Route Continue extension model calls through Orq.ai AI Gateway for unified observability, cost tracking, and model governance. Route [**Continue**](https://marketplace.visualstudio.com/items?itemName=Continue.continue) model calls through the [**Orq.ai AI Gateway**](/docs/ai-gateway/get-started/introduction). **Continue** is an open-source coding assistant extension for VS Code that supports custom OpenAI-compatible providers. Requests appear in [Traces](/docs/ai-gateway/traces) automatically. ## Prerequisites * [Visual Studio Code](https://code.visualstudio.com/) installed * [**Continue**](https://marketplace.visualstudio.com/items?itemName=Continue.continue) extension installed * Active **Orq.ai** account with **AI Gateway** access * [**Orq.ai** API key](/docs/ai-studio/organization/api-keys) * Model enabled in [**AI Gateway** → **Supported Models**](/docs/ai-gateway/supported-models) ## Setup Create `~/.continue/config.yaml` if it does not exist. If the file already has a `models` list, add the **Orq.ai** entry as a new item: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} models: - name: Orq Gateway provider: openai model: openai/gpt-5.4 apiBase: https://my.orq.ai/v3/router apiKey: ``` Replace `` with an [API key](/docs/ai-studio/organization/api-keys). To avoid storing the key in plain text, use `apiKey: $ORQ_API_KEY` and export the variable in the shell that launched VS Code. Use the `code` command to open VS Code from the terminal; VS Code launched from Dock or Spotlight does not inherit shell variables. Keep `openai/gpt-5.4` to route requests to that model through the **AI Gateway**, or replace it with any provider-prefixed model ID enabled in [**AI Gateway** → **Supported Models**](/docs/ai-gateway/supported-models) (e.g. `anthropic/claude-sonnet-5`). Open the **Continue** panel (+Shift+L / Ctrl+Shift+L) and select **Orq Gateway** from the model picker at the bottom of the chat input. If **Orq Gateway** does not appear, check the top of the **Continue** panel for a config error banner. ## Configuration Reference | Field | Value | Description | | ---------- | ----------------------------- | -------------------------------------------------------------------------- | | `name` | Any display label | Name shown in the **Continue** model picker. | | `provider` | `openai` | Tells **Continue** to use the OpenAI-compatible wire format. | | `model` | `provider/model-id` | Provider-prefixed model ID (e.g. `anthropic/claude-sonnet-5`). | | `apiBase` | `https://my.orq.ai/v3/router` | **AI Gateway** router endpoint. | | `apiKey` | **Orq.ai** API key | Sent as the Bearer token. Use `$ORQ_API_KEY` to read from the environment. | ## Skills **Orq Skills** add pre-built agentic workflows to Continue for the full Build, Evaluate, Optimize lifecycle. ### Installation ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} npx skills add orq-ai/assistant-plugins --agent continue ``` Set an [API key and connect the Orq MCP server](/docs/ai-studio/integrations/code-assistants/orq-skills#prerequisites) first. Skills are then triggered by describing what is needed. The full catalogue of skills and slash commands. Slash commands (`/orq:quickstart`, `/orq:traces`, and others) are only available in Claude Code. ## Troubleshooting Confirm `~/.continue/config.yaml` was saved correctly. Open the **Continue** panel and check for a config error banner at the top. It displays YAML parse errors that prevent models from loading. Reload VS Code if the file was edited while **Continue** was already running. Confirm the `apiKey` value is a valid **Orq.ai** API key. To avoid storing the key in plain text, set `apiKey: $ORQ_API_KEY` and export the variable in the shell that launched VS Code. The model must be enabled in [**AI Gateway** → **Supported Models**](/docs/ai-gateway/supported-models) and the `model` field must use the provider-prefixed format (e.g. `openai/gpt-5.4`, not `gpt-5.4`). Confirm `apiBase` is `https://my.orq.ai/v3/router` with no trailing slash. Verify **Orq Gateway** is selected in the **Continue** model picker before sending a prompt. ## Verification Send a prompt in the **Continue** panel. The response appears in VS Code and a new row appears within a few seconds in [**Orq.ai** Traces](/docs/ai-gateway/traces), showing the model identifier (e.g. `openai/gpt-5.4`) and token usage. # LiteLLM Source: https://docs.orq.ai/docs/ai-gateway/integrations/frameworks/litellm Send LiteLLM traces to Orq.ai using OpenTelemetry instrumentation. Monitor LLM calls, costs, and latency across all providers LiteLLM supports. Instrument application code with OpenTelemetry to capture traces, logs, and metrics for every LLM call, agent step, and tool use. To retire a **LiteLLM** proxy rather than instrument it, see [Migrate to Orq.ai](/docs/ai-gateway/get-started/migrate#migrate-from-litellm). ## Observability ### Getting Started LiteLLM provides a unified interface for multiple LLM providers, enabling seamless switching between OpenAI, Anthropic, Cohere, and 100+ other providers. Tracing LiteLLM with **Orq.ai** provides comprehensive insights into provider performance, cost optimization, routing decisions, and API reliability across a multi-provider setup. ### Prerequisites Ensure the following prerequisites are in place: * An **Orq.ai** account and [API Key](/docs/ai-studio/organization/api-keys) * LiteLLM installed in the project * Python 3.8+ * API keys for the LLM providers (OpenAI, Anthropic, Cohere, etc.) ### Install Dependencies ```bash Bash theme={"theme":{"light":"github-light","dark":"github-dark"}} pip install 'litellm[proxy]' ``` ```bash Bash theme={"theme":{"light":"github-light","dark":"github-dark"}} pip install litellm opentelemetry-sdk opentelemetry-exporter-otlp openinference-instrumentation-litellm ``` ### Configure Orq.ai Set the following environment variables to connect to the **Orq.ai** OpenTelemetry collector: ```bash Unix/Linux/macOS theme={"theme":{"light":"github-light","dark":"github-dark"}} export ORQ_API_KEY="" export OTEL_EXPORTER_OTLP_ENDPOINT="https://my.orq.ai/v2/otel/v1/traces" export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $ORQ_API_KEY" export OTEL_RESOURCE_ATTRIBUTES="service.name=litellm-app,service.version=1.0.0" export LITELLM_MASTER_KEY="" # Provider API keys: add only the ones you use export OPENAI_API_KEY="" export ANTHROPIC_API_KEY="" export COHERE_API_KEY="" export GOOGLE_API_KEY="" ``` ```bash Windows (PowerShell) theme={"theme":{"light":"github-light","dark":"github-dark"}} $env:ORQ_API_KEY = "" $env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://my.orq.ai/v2/otel/v1/traces" $env:OTEL_EXPORTER_OTLP_HEADERS = "Authorization=Bearer $env:ORQ_API_KEY" $env:OTEL_RESOURCE_ATTRIBUTES = "service.name=litellm-app,service.version=1.0.0" $env:LITELLM_MASTER_KEY = "" # Provider API keys: add only the ones you use $env:OPENAI_API_KEY = "" $env:ANTHROPIC_API_KEY = "" $env:COHERE_API_KEY = "" $env:GOOGLE_API_KEY = "" ``` ```bash .env theme={"theme":{"light":"github-light","dark":"github-dark"}} ORQ_API_KEY= OTEL_EXPORTER_OTLP_ENDPOINT=https://my.orq.ai/v2/otel/v1/traces OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer OTEL_RESOURCE_ATTRIBUTES=service.name=litellm-app,service.version=1.0.0 LITELLM_MASTER_KEY= # Provider API keys: add only the ones you use OPENAI_API_KEY= ANTHROPIC_API_KEY= COHERE_API_KEY= GOOGLE_API_KEY= ``` ### Integrations `litellm.callbacks = ["otel"]` only emits spans when running inside **LiteLLM Proxy Server**. In a standalone Python script it logs a warning and skips OTel initialisation. No spans reach **Orq.ai**. Choose the setup that matches the environment below. Run the LiteLLM Proxy Server with the `otel` callback enabled. The proxy handles all OTel export using the environment variables configured above. ```yaml config.yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} model_list: - model_name: gpt-5.6-sol litellm_params: model: openai/gpt-5.6-sol api_key: os.environ/OPENAI_API_KEY litellm_settings: callbacks: ["otel"] router_settings: pass_through_all_models: true general_settings: master_key: os.environ/LITELLM_MASTER_KEY ``` ```bash Bash theme={"theme":{"light":"github-light","dark":"github-dark"}} litellm --config config.yaml ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="http://localhost:4000", api_key=os.getenv("LITELLM_MASTER_KEY"), ) response = client.chat.completions.create( model="gpt-5.6-sol", messages=[{"role": "user", "content": "Hello, how are you?"}], ) print(response.choices[0].message.content) ``` Use `openinference-instrumentation-litellm` for automatic OpenTelemetry tracing without running a proxy: ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from openinference.instrumentation.litellm import LiteLLMInstrumentor import litellm import os tracer_provider = TracerProvider( resource=Resource({"service.name": "litellm-app"}) ) otlp_exporter = OTLPSpanExporter( endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"), headers={"Authorization": f"Bearer {os.getenv('ORQ_API_KEY')}"}, ) tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter)) LiteLLMInstrumentor().instrument(tracer_provider=tracer_provider) response = litellm.completion( model="gpt-5.6-sol", messages=[{"role": "user", "content": "Hello, how are you?"}], ) print(response.choices[0].message.content) ``` All LiteLLM calls will be automatically instrumented and exported to **Orq.ai** through the OTLP exporter. For more details, see [Traces](/docs/ai-studio/observability/traces). ### Examples **Basic Multi-Provider Usage** ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="http://localhost:4000", api_key=os.getenv("LITELLM_MASTER_KEY"), ) def basic_multi_provider_example(): models = [ "gpt-5.6-sol", "claude-opus-5", "command-r", "gemini/gemini-3.5-flash", "ollama/llama3.2", ] prompt = "Explain the benefits of microservices architecture in 2 sentences." results = [] for model in models: try: print(f"Testing {model}...") response = client.chat.completions.create( model=model, messages=[{"role": "user", "content": prompt}], max_tokens=150, temperature=0.7, ) results.append({ "model": model, "content": response.choices[0].message.content, "tokens": response.usage.total_tokens, "cost": response.usage.total_tokens * 0.002, }) except Exception as e: print(f"Error with {model}: {e}") results.append({"model": model, "error": str(e)}) return results results = basic_multi_provider_example() for result in results: if "error" not in result: print(f"{result['model']}: {result['tokens']} tokens, ~${result['cost']:.4f}") ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from openinference.instrumentation.litellm import LiteLLMInstrumentor import litellm import os tracer_provider = TracerProvider( resource=Resource({"service.name": "litellm-app"}) ) otlp_exporter = OTLPSpanExporter( endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"), headers={"Authorization": f"Bearer {os.getenv('ORQ_API_KEY')}"}, ) tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter)) LiteLLMInstrumentor().instrument(tracer_provider=tracer_provider) def basic_multi_provider_example(): models = [ "gpt-5.6-sol", "claude-opus-5", "command-r", "gemini/gemini-3.5-flash", "ollama/llama3.2", ] prompt = "Explain the benefits of microservices architecture in 2 sentences." results = [] for model in models: try: print(f"Testing {model}...") response = litellm.completion( model=model, messages=[{"role": "user", "content": prompt}], max_tokens=150, temperature=0.7, ) results.append({ "model": model, "content": response.choices[0].message.content, "tokens": response.usage.total_tokens, "cost": response.usage.total_tokens * 0.002, }) except Exception as e: print(f"Error with {model}: {e}") results.append({"model": model, "error": str(e)}) return results results = basic_multi_provider_example() for result in results: if "error" not in result: print(f"{result['model']}: {result['tokens']} tokens, ~${result['cost']:.4f}") ``` **Cost Optimization with Provider Fallback** ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI from typing import List, Dict, Any import os client = OpenAI( base_url="http://localhost:4000", api_key=os.getenv("LITELLM_MASTER_KEY"), ) def cost_optimized_completion( messages: List[Dict[str, str]], fallback_models: List[str] = None, max_tokens: int = 100, ) -> Dict[str, Any]: if fallback_models is None: fallback_models = [ "gpt-5.4-mini", "claude-haiku-4-5", "command", "gpt-5.6-sol", "claude-sonnet-5", ] for i, model in enumerate(fallback_models): try: print(f"Attempting {model} (priority {i+1})...") response = client.chat.completions.create( model=model, messages=messages, max_tokens=max_tokens, temperature=0.7, ) # approximate values: check provider pricing pages for current rates cost_per_1k_tokens = { "gpt-5.4-mini": 0.0015, "gpt-5.6-sol": 0.005, "claude-haiku-4-5": 0.00025, "claude-sonnet-5": 0.003, "command": 0.015, } estimated_cost = (response.usage.total_tokens / 1000) * cost_per_1k_tokens.get(model, 0.002) return { "success": True, "model_used": model, "content": response.choices[0].message.content, "tokens": response.usage.total_tokens, "estimated_cost": estimated_cost, "attempt_number": i + 1, } except Exception as e: print(f"Failed with {model}: {e}") if i == len(fallback_models) - 1: return { "success": False, "error": f"All models failed. Last error: {e}", "attempts": len(fallback_models), } continue return {"success": False, "error": "No models available"} result = cost_optimized_completion([ {"role": "user", "content": "Summarize the key benefits of using Docker containers for development"} ]) if result["success"]: print(f"Success with {result['model_used']} on attempt {result['attempt_number']}") print(f"Cost: ~${result['estimated_cost']:.4f}, Tokens: {result['tokens']}") print(f"Response: {result['content'][:100]}...") ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from openinference.instrumentation.litellm import LiteLLMInstrumentor import litellm from typing import List, Dict, Any import os tracer_provider = TracerProvider( resource=Resource({"service.name": "litellm-app"}) ) otlp_exporter = OTLPSpanExporter( endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"), headers={"Authorization": f"Bearer {os.getenv('ORQ_API_KEY')}"}, ) tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter)) LiteLLMInstrumentor().instrument(tracer_provider=tracer_provider) def cost_optimized_completion( messages: List[Dict[str, str]], fallback_models: List[str] = None, max_tokens: int = 100, ) -> Dict[str, Any]: if fallback_models is None: fallback_models = [ "gpt-5.4-mini", "claude-haiku-4-5", "command", "gpt-5.6-sol", "claude-sonnet-5", ] for i, model in enumerate(fallback_models): try: print(f"Attempting {model} (priority {i+1})...") response = litellm.completion( model=model, messages=messages, max_tokens=max_tokens, temperature=0.7, ) # approximate values: check provider pricing pages for current rates cost_per_1k_tokens = { "gpt-5.4-mini": 0.0015, "gpt-5.6-sol": 0.005, "claude-haiku-4-5": 0.00025, "claude-sonnet-5": 0.003, "command": 0.015, } estimated_cost = (response.usage.total_tokens / 1000) * cost_per_1k_tokens.get(model, 0.002) return { "success": True, "model_used": model, "content": response.choices[0].message.content, "tokens": response.usage.total_tokens, "estimated_cost": estimated_cost, "attempt_number": i + 1, } except Exception as e: print(f"Failed with {model}: {e}") if i == len(fallback_models) - 1: return { "success": False, "error": f"All models failed. Last error: {e}", "attempts": len(fallback_models), } continue return {"success": False, "error": "No models available"} result = cost_optimized_completion([ {"role": "user", "content": "Summarize the key benefits of using Docker containers for development"} ]) if result["success"]: print(f"Success with {result['model_used']} on attempt {result['attempt_number']}") print(f"Cost: ~${result['estimated_cost']:.4f}, Tokens: {result['tokens']}") print(f"Response: {result['content'][:100]}...") ``` ### View Traces Head to the [Traces](/docs/ai-studio/observability/traces) tab to view LiteLLM traces in the AI Studio. View Traces ## Evaluations & Experiments Once agents are running, use **Evaluatorq** to score outputs across a dataset and **Experiments** to compare configurations side-by-side. Run parallel evaluations across deployed agents and compare results. Compare agent configurations and view results in the AI Studio. # MCP Gateway Source: https://docs.orq.ai/docs/ai-gateway/mcp-portal/mcp-gateways Bundle multiple MCP servers behind a single MCP Gateway endpoint with a unified tool surface for MCP-compatible clients. Create a single client-facing MCP endpoint that bundles one or more **MCP Servers**. Clients connect to the MCP Gateway URL and get a unified tool surface without knowing individual server URLs or authentication details. The gateway acts as a **virtual MCP server**: it aggregates tools from multiple upstream servers behind one address. This page covers **MCP Gateways**, client-facing endpoints that bundle upstream **MCP Servers** Orq connects to. This is a different feature from [Orq's own MCP server](/docs/ai-studio/integrations/code-assistants/orq-mcp), which coding assistants connect to for workspace administration. Navigate to **AI Gateway > MCP Portal** in the sidebar. The **MCP Gateway** tab lists every MCP Gateway in the workspace. ## How It Works A **virtual MCP server** is a single endpoint that gives clients access to tools from multiple upstream **MCP Servers**. The gateway aggregates these servers behind one address, handles authentication and routing, and resolves tool-name conflicts internally. Clients connect to one URL and see all linked tools as if they came from one provider. ## Use Cases * Exposing multiple MCP servers through a single endpoint for clients that cannot manage multiple connections. * Aliasing server tools under consistent names so clients do not break when upstream tool names change. * Governing tool access per team: admins build an MCP Gateway from a selected subset of tools across several **MCP Servers**, so the Marketing team and the Research team can share the same servers while reaching different tools. ## Set Up an MCP Gateway 1. Click Create MCP Gateway. Create MCP Gateway form showing General section with Description and Key, and Tools section with Tool exposure mode and Tool naming 2. **Fill in the general details.** * **Description** (optional): a note about what this MCP Gateway provides. * **Key** (required): a unique identifier. Used in the MCP Gateway URL. Cannot be changed after creation. 3. **Configure tool settings.** * **Tool exposure mode**: how the MCP Gateway presents tools to connected clients. **Code mode** (default) exposes a single tool provided by the MCP Gateway that handles discovery and execution internally. **Direct mode** exposes each upstream tool individually. * **Tool naming**: how tool names are namespaced across servers. **Always prefix with server key** (default) prefixes all tools with the server's key, or its alias if one is set per-link. **Prefix on collision** only prefixes conflicting names. 4. Click Create. ## After Creation **Orq.ai** redirects to the **MCP Servers** tab of the new MCP Gateway. Click + Add server to link any server already configured in [MCP Servers](/docs/ai-gateway/mcp-portal/mcp-servers). Only servers that have completed their initial sync can be linked. Link only **MCP Servers** whose project access covers the **MCP Gateway** project access. An **MCP Gateway** on **All projects** therefore needs **MCP Servers** on **All projects**. MCP Gateway detail page on the MCP Servers tab listing the tomtom-maps server expanded to show 3 of 18 tools exposed, with Traffic, Alias and Connection panels on the right The MCP Gateway detail page has three tabs: * **Overview**: traffic for the MCP Gateway and the **Connect** section with the command clients use to reach it. * **MCP Servers**: linked servers and the tools each one exposes. Select a tool checkbox to expose it through the MCP Gateway, or clear it to stop exposing it. Both take effect immediately, with no save action. The side panel shows **Traffic** for the selected server, its **Alias** (the prefix that namespaces the server's tools), and its **Connection** details: server key, type, base URL, auth, and last sync time. * **Settings**: the MCP Gateway description and tool settings, editable after creation. ## Toolsets **Toolsets** are named collections of tools from one or more linked servers. Create a toolset from the MCP Gateway detail page by adding tools from linked servers. Attach a toolset to an [Agent](/docs/ai-studio/ai-engineering/build-agents) instead of listing individual tools. When the upstream server adds or removes tools, the toolset picks up the changes on the next sync. | Field | Description | | ---------------- | ----------------------------------------------------------------------------- | | **Key** | Unique identifier within the project. | | **Display name** | Human-readable label. | | **Description** | What the toolset covers. | | **Project** | The project the toolset belongs to. | | **Tools** | List of server + tool name pairs. Servers must be linked to this MCP Gateway. | ## Connect a Client Any MCP-compatible client can reach the MCP Gateway, most often a coding agent such as [Claude Code](/docs/ai-studio/integrations/code-assistants/claude-code), [Cursor](/docs/ai-studio/integrations/code-assistants/cursor), or [Codex](/docs/ai-studio/integrations/code-assistants/codex). See [Coding Agents](/docs/ai-studio/integrations/overview) for the full list. The **Connect** section on the **Overview** tab holds the command that registers the MCP Gateway with the agent. Set `ORQ_API_KEY`, then run it. The examples below use `https://my.orq.ai` as the default host. For self-hosted or on-premise deployments, replace this with the base URL configured for your deployment. The API's `public_url` field returns the full connect endpoint (which may be relative when no public base URL is configured). Connect section showing an npx add-mcp command with the MCP Gateway URL, a name flag, and an Authorization bearer header ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} npx add-mcp https://my.orq.ai/v3/mcp/ \ --name \ --header "Authorization: Bearer $ORQ_API_KEY" ``` The same endpoint registers in any MCP-compatible client. Common coding agents: Register the MCP Gateway with Claude Code: ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} claude mcp add --transport http https://my.orq.ai/v3/mcp/ --header "Authorization: Bearer ${ORQ_API_KEY}" ``` Replace `` with the gateway key from the **Connect** section and set `ORQ_API_KEY` to an [API key](/docs/ai-studio/organization/api-keys) before running the command. Confirm with `claude mcp list`. Add the MCP Gateway in **Cursor Settings → Tools & MCP → New MCP Server** and paste: ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} { "mcpServers": { "": { "url": "https://my.orq.ai/v3/mcp/", "headers": { "Authorization": "Bearer YOUR_ORQ_API_KEY" } } } } ``` Replace `` with the gateway key from the **Connect** section and `YOUR_ORQ_API_KEY` with an [API key](/docs/ai-studio/organization/api-keys). Save the configuration; the server connects and shows a green indicator. Register the MCP Gateway with Codex: ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} codex mcp add --url https://my.orq.ai/v3/mcp/ --bearer-token-env-var ORQ_API_KEY ``` Replace `` with the gateway key from the **Connect** section and export `ORQ_API_KEY` with an [API key](/docs/ai-studio/organization/api-keys) in the shell that launches Codex. Add the MCP Gateway with the **MCP: Add Server** command, select **HTTP (HTTP or Server-Sent Events)**, and enter `https://my.orq.ai/v3/mcp/` as the server URL. VS Code writes a `.vscode/mcp.json`; replace its contents with: ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} { "inputs": [ { "type": "promptString", "id": "orq-api-key", "description": "Orq.ai API Key", "password": true } ], "servers": { "": { "url": "https://my.orq.ai/v3/mcp/", "type": "http", "headers": { "Authorization": "Bearer ${input:orq-api-key}" } } } } ``` Replace `` with the gateway key from the **Connect** section. VS Code prompts for the [API key](/docs/ai-studio/organization/api-keys) on first connect and stores it in the OS secret store. ## Monitor Usage The **Overview** tab reports how clients use the MCP Gateway. It opens on the last 7 days, and every card compares that window against the one before it. Change the window with the time range picker in the top right. MCP Gateway Overview showing Tool calls, Success, Errors and P95 latency cards above a Tool calls by outcome bar chart and a Tools leaderboard of the most called tools | Card | Description | | --------------- | --------------------------------------------------- | | **Tool calls** | Tool invocations routed through the MCP Gateway. | | **Success** | Invocations that completed successfully. | | **Errors** | Invocations that returned an error. | | **P95 latency** | 95th percentile duration of successful invocations. | **Traffic and reliability** breaks the same window down further: * **Tool calls by outcome**: call volume over time, split into successes and errors. * **Tools leaderboard**: the most called tools with their call count and P95 latency, listed under their namespaced names. ## Audit Changes Changes to the MCP Gateway are recorded in [Audit Logs](/docs/ai-studio/organization/audit-logs), so a change to the exposed tool surface can be traced back to whoever made it. Entries are written under two entity types: | Entity type | Recorded when | | --------------- | ----------------------------------------------------------------------------------- | | **MCP Gateway** | A gateway is created, updated, or deleted, including changes to its linked servers. | | **MCP Server** | A server is created, updated, or deleted. | Filter by either entity type on the Audit Logs page to see only MCP Portal activity. # MCP Servers Source: https://docs.orq.ai/docs/ai-gateway/mcp-portal/mcp-servers Connect upstream MCP servers to Orq.ai. Discover tools automatically and expose them to Agents and Gateways. Connect upstream [Model Context Protocol](https://modelcontextprotocol.io) servers to **Orq.ai**. Once connected, tool discovery happens automatically and the tools become available to **Agents** and **Gateways**. These are third-party **MCP Servers** that Orq connects to. This is a different feature from [Orq's own MCP server](/docs/ai-studio/integrations/code-assistants/orq-mcp), which coding assistants connect to for workspace administration. Navigate to **AI Gateway > MCP Portal** in the sidebar. The **MCP Servers** tab lists all registered MCP servers. Click **Create MCP Server** to add one. MCP connections were previously created as an **MCP Tool** under **Create Tools**. That tool type is retired, and existing MCP Tools now appear here in the **MCP Portal**. ## Use Cases * Connecting third-party SaaS tools (Slack, Linear, Figma, etc.) to agents without writing custom integrations. * Exposing internal APIs as callable tools for agents. * Centralizing MCP server management instead of configuring connections per tool or per agent. * Sharing a single MCP server across multiple agents and [gateways](/docs/ai-gateway/mcp-portal/mcp-gateways) with controlled tool exposure. ## How It Works Each MCP Server registers an upstream endpoint. **Orq.ai** connects to it, discovers the available tools, and tracks the sync state. Tools can be exposed to all agents, filtered by an allow-list, or hidden entirely. When a server is linked to a [gateway](/docs/ai-gateway/mcp-portal/mcp-gateways), the gateway handles tool routing and authentication so clients only need the gateway URL. ## Set Up an MCP Server 1. Click Create MCP Server. Create MCP Server form showing Provider, General, Connection, Authentication, and Verify connection sections 2. **Pick a provider or enter a custom URL.** The **MCP Provider** dropdown lists pre-configured providers (Airtable, Slack, Linear, and more). Selecting one pre-fills the connection details and authentication headers. Choose **Custom** to enter a server URL manually. 3. **Fill in the general details.** * **Key** (required): a unique identifier for this server. Used in tool name prefixes when linked to a [gateway](/docs/ai-gateway/mcp-portal/mcp-gateways). * **Description** (optional): a note about what this server provides. 4. **Set the connection.** * **Type**: **HTTP** (default) or **SSE** for streaming-capable servers. * **URL** (required): the endpoint of the upstream MCP server. 5. **Configure authentication.** * **None** (default): for public servers that require no credentials. * **Static Headers**: send fixed headers with every request. Use `{{variable}}` syntax for sensitive values like API keys. The actual values are stored securely and used at runtime when executing tools through agents or gateways. * **OAuth Client Credentials**: authenticate with an upstream OAuth 2.0 authorization server using the client credentials grant. The gateway exchanges the credentials for an access token, caches it, and attaches it to every upstream request. Fill in the **Client ID**, **Client Secret**, and **Token URL** (the endpoint that issues tokens, for example `https://accounts.example.com/oauth/token`). The client secret is stored securely and never returned in API responses. 6. **Test the connection.** Click **Test connection** to probe the server before saving. This validates the URL and authentication, and returns the list of discovered tools. 7. Click Create. ## After Creation Once saved, **Orq.ai** syncs with the upstream server and discovers its tools. The server detail page shows discovered tools on the left and tool details on the right. MCP Server detail page showing a list of discovered tools on the left and the search_entities tool arguments, execution panel, and Run tool button on the right From the server detail page: * **View discovered tools** in the **Tool Sets** tab. Select a tool to see its description, arguments, and input schema. The sync state shows the total tool count, tools added and removed since the last sync, the last synced timestamp, and any sync errors. * **Control tool exposure** in the **Settings** tab. Set the mode to **All** (expose every discovered tool), **Selected** (expose only tools in an allow-list), or **None** (hide all tools). Mark individual tools as **read-only** to prevent write operations. * **Test a tool** by filling in the arguments and clicking **Run tool**. The test invokes the tool on the upstream server and shows the response. * **Re-sync** to pick up changes from the upstream server. New tools are added, removed tools are dropped. * **Configure project access** in the **Settings** tab. Servers are scoped to a project. Use sharing settings to control which team members can view or edit the server. * **View version history** in the **Versions** tab. * **Duplicate** a server to create a copy with the same connection and tool settings under a new key. ## Monitor Usage The **Overview** tab reports tool usage for the server, aggregated across every gateway that exposes it. It opens on the last 7 days, and the badge next to the heading carries the sync state, the discovered tool count, and the time of the last sync. MCP Server Overview with a Synced badge reading 65 tools, four metric cards, a bar chart of tool calls by outcome, and a leaderboard of the most called tools The cards and charts match the ones on the gateway **Overview**. See [Monitor Usage](/docs/ai-gateway/mcp-portal/mcp-gateways#monitor-usage) for what each one measures. The scope differs: these numbers cover the server across every gateway it is linked to, rather than a single gateway. ## Attach to an Agent Once a server is synced and its tools are exposed, attach it to an **Agent** so the model can call those tools. See [Build Agents](/docs/ai-studio/ai-engineering/build-agents) for the full setup. ## Attach to a Gateway Link a server to an **MCP Gateway** to bundle it with other servers behind a single client-facing endpoint. See [MCP Gateway](/docs/ai-gateway/mcp-portal/mcp-gateways) for details. # Model Arena Source: https://docs.orq.ai/docs/ai-gateway/model-arena Rank models head-to-head on real prompts with orq-arena, the Orq.ai benchmarking CLI. Pairwise LLM jury, Bradley-Terry ratings, confidence intervals. Public leaderboards rank models on someone else's data. Model Arena ranks them on the prompts that matter to the workload. **orq-arena** runs a round-robin tournament over a pool of models, has an LLM jury compare every pair of answers blind, and produces a chess-style Elo ladder with confidence intervals. Three parts make up every run: * **Candidates**: the model pool under test, any size from two upwards, called through the [**AI Gateway**](/docs/ai-gateway/using-the-router) so one API key covers every provider * **Jury**: a panel of judge models that compares the two answers, scoring each pair twice with the seats swapped * **Rating**: a Bradley-Terry fit over every judged round, reported with bootstrapped 95% confidence intervals Use it to pick a default model for a product, re-rank the pool when a new model ships, generate pairwise preference data, check whether reasoning helps on a given workload, or choose the strong and economical pair for the [**Smart Router**](/docs/ai-gateway/smart-router). A judge that votes differently when only the seat order changes abstains for that round, and the flip is recorded. A round needs at least two surviving votes, a configurable threshold, and those votes must reach a strict plurality. Rounds that fall short are recorded as **inconclusive** and never reach the rating, so a small run reports fewer rated rounds rather than a ranking built on coin flips. This page covers installing **orq-arena**, running a benchmark, and reading the results. For every command and flag, every `orq_arena.yaml` key, and the full scoring methodology, see the [**orq-arena** documentation](https://orq-ai.github.io/orq-arena/). ## Prerequisites Python 3.10 or later, [uv](https://docs.astral.sh/uv/getting-started/installation/), Git, and an **Orq.ai** workspace. Config validation requires at least two candidates and a non-empty judge panel, so enable the models for both before running; the shipped config expects its full eight-model pool and three judges. **orq-arena** is an open-source CLI installed from GitHub rather than a published package: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} git clone https://github.com/orq-ai/orq-arena.git cd orq-arena uv tool install . cp .env.example .env ``` Add a workspace API key to `.env`, created per the [API keys guide](/docs/ai-studio/organization/api-keys): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} ORQ_API_KEY=your-orq-api-key ``` Every candidate call, judge call, and preflight probe routes through the **AI Gateway** with this one key. A variable already set in the shell wins over the file. Although `uv tool install` puts `orq-arena` on the PATH globally, it reads `.env`, the config, and the prompts file relative to the working directory. Run it from the cloned repository, or pass absolute paths to `--config` and `--prompts`. ## First benchmark run ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq-arena run --config orq_arena.yaml ``` The shipped config carries an eight-model pool and a three-judge panel. Every pair of candidates meets once in a full round-robin, both answers stream through the router, and the jury votes in both seat orders. Rounds are written to the battle log as they resolve, so an interrupted run keeps every round it already paid for, though re-running starts a fresh tournament rather than resuming: `run` refuses to overwrite a non-empty log unless `--overwrite` is passed. At the end, the standings print and the HTML report is written next to the log. Before any of that, the preflight prints the exact call counts and a RUN PLAN table, one row per candidate and judge with its call count, catalog price, and projected cost. It then stops on a confirmation carrying both figures: ``` Proceed (≈ $11.87, up to $36.70 with retries)? [y/N]: ``` Nothing has been spent at that point beyond a few tiny probe calls. The projection assumes every response hits its token cap, so a typical run costs less; the worst case adds a retry on every stream and a stand-in judge on every judge call. Neither is a hard cap, since prompt tokens are estimated from characters. Scripted runs need `-y`, since a pipe cannot answer the confirmation. Pool size drives cost quadratically. An N-model pool runs N×(N-1)/2 matches, so eight models is 28 matches and sixteen is 120. Trimming the pool saves far more than trimming rounds. No API key yet? A recorded run is committed in the repository. `orq-arena report examples/quickstart/battles.jsonl` rebuilds its report with no key and no network calls. ## Choosing the model pool The pool is the `candidates` list in the config passed to `--config`, using **AI Gateway** model IDs. `orq-arena refresh-catalog --show` lists the models enabled in the workspace, grouped by provider. ```yaml orq_arena.yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} candidates: # the model pool: any size >= 2 - model_id: anthropic/claude-sonnet-4-6 - model_id: openai/gpt-5.4 - model_id: deepseek/deepseek-chat - model_id: google/gemini-3.5-flash reasoning: { thinking: { type: disabled } } # per-model overrides inline judges: # the jury; every pair judged in both seat orders - anthropic/claude-haiku-4-5-20251001 - google/gemini-2.5-flash-lite - openai/gpt-5.4-nano match: max_rounds: 5 # prompts judged per match ``` `reasoning` fields are forwarded to the router verbatim, so each provider takes its own shape; the [configuration reference](https://orq-ai.github.io/orq-arena/configuration/) lists the per-provider recipes. The repository also ships preset pools under `configs/` for frontier, budget, and sixteen-model fields, plus `configs/reasoning_arena.yaml`, the thinking-on counterpart to the thinking-off default, for measuring whether reasoning earns its cost. Judges from the same provider family as a candidate favor their own family's prose, and seat swapping does not correct for it. The preflight warns when it detects an overlap. A jury drawn entirely from families outside the pool is the clean setup, worth arranging before publishing a ranking. ## Supplying prompts The shipped `prompts/starter.jsonl` is a 30-prompt smoke test sized to exercise every mechanism, not to defend a ranking. Swap it with `--prompts`. One JSON object per line. `prompt` is the only required field; `category` is optional and feeds per-category ratings. Any other keys ride along into the battle log, so results can be joined back to the source data. ```json prompts.jsonl theme={"theme":{"light":"github-light","dark":"github-dark"}} {"prompt": "Write a Python function that finds the longest palindromic substring.", "category": "code"} {"prompt": "Summarize the key trade-offs between SQL and NoSQL for a startup.", "category": "reasoning"} ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq-arena run --config orq_arena.yaml --prompts prompts.jsonl ``` Pass `orq:` to run over a [Dataset](/docs/ai-studio/optimize/datasets) from the workspace with the same API key, nothing to export. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq-arena run --config orq_arena.yaml --prompts orq:my_dataset_id ``` Each datapoint's last `user` message becomes a prompt, with `{{var}}` placeholders filled from its `inputs`. The manifest records the dataset's ID, name, and studio URL, and the report links it by name, so a shared report traces back to the data it ran on. `match.max_rounds` caps how many prompts each match judges. With the default of `5` against the 30-prompt starter bank, every match sees a seeded random slice of five. Pass `--rounds 30` to use every prompt in every match. ## Where results land Every run writes three files, named after the battle log and written beside it (the working directory by default, or wherever `--output` points): `battles.jsonl` (one line per judged round, with both responses, per-judge votes, and token accounting), `battles.run.json` (the manifest: config, content hashes, panel, seed, agreement stats, no credentials), and `battles.report.html` (a single-file report, no server or external assets, safe to share). Everything downstream works from the battle log alone. `orq-arena report` rebuilds the page, `annotate` renders a blind version for human raters and `anchor` scores their votes against the panel, and `rejudge` re-scores the recorded responses with a different jury for judge tokens only, reporting the rank correlation against the original. That last one is the direct test of whether a ranking depends on who judged it. ## Reading the report The report opens on a verdict, then the Elo ladder with error bars, a quality-versus-cost value map, speed, and dollar spend. orq-arena HTML report headed "top spot not resolved at this size", reporting that gemini-3.5-flash leads claude-sonnet-4-6 but 140 rounds cannot separate them, above a leaderboard of eight models with ELO, 95% confidence interval, a shared-scale interval bar, length-adjusted rating, and average answer length. Two columns carry most of the meaning: * **95% CI** comes from a seeded 1000-resample bootstrap, plotted on one shared scale. The intervals are marginal, so read them one at a time: two of them overlapping does not mean the two models are tied, or even that they are indistinguishable. * **Length-adj.** prices out the jury's preference for longer answers. A large gap between a model's rating and its length-adjusted rating means verbosity, not quality, was doing the separating. The column appears only when that preference is identified: the fit converged, and a bootstrap interval over the same rounds excludes zero. When the top two cannot be separated, the report says so and reports how often the leader came out ahead across resamples, instead of declaring a winner the data does not support. ## How far to trust a run Every run publishes the numbers needed to challenge its own ranking: mean judge agreement, chance-corrected kappa, and each judge's flip rate between seat orders. Three things are worth knowing before quoting a result: * **A failed stream never becomes a verdict.** One retry per side, then the round is voided: logged, but never judged or rated. Truncated answers are judged as-is and flagged, so the jury sees what a reader would see. * **Small runs produce wide intervals**, and that is the honest output. The top spot counts as separated only when the bootstrapped interval on the *difference* between the top two excludes zero. * **The defaults are a smoke test.** A ranking worth defending takes a real prompt set, hundreds of rounds, and judges from families outside the pool. The [methodology reference](https://orq-ai.github.io/orq-arena/methodology/) covers the Bradley-Terry fit, the bootstrap, length control, and the human-anchor workflow in full. ## After the run Model Arena keeps its results in the three local files rather than writing them back to **Orq.ai**. Candidates and judges share one router client, so every call a tournament makes is recorded in [Traces](/docs/ai-studio/observability/traces) alongside the workspace's other **AI Gateway** traffic, with its own latency, token usage, and cost. Share the HTML report, then act on the ranking: pin the winner in a [Deployment](/docs/ai-studio/ai-engineering/deployments), or put the strongest and most economical models into a [**Smart Router**](/docs/ai-gateway/smart-router) pool and let it choose per request. ## Going further Install, credentials, first tournament, and troubleshooting. Every command and flag with its expected output. Every `orq_arena.yaml` key, its type and default, plus the prompts file format. Example runs, preset model pools, and the implementation. MIT licensed. Step-by-step walkthrough: benchmark a flagship model against two cheaper alternatives, and watch the length-adjusted ranking reverse the standings. # Model FAQ Source: https://docs.orq.ai/docs/ai-gateway/model-faq Answers to common questions about models in Orq.ai: enabling, IDs, parameters, reasoning, and capabilities. Frequently asked questions about models and their capabilities in **Orq.ai**. Each answer links to the page that covers the topic in full. Browse the full catalog on [Supported Models](/docs/ai-gateway/supported-models). Model IDs use the `provider/` prefix, for example `openai/gpt-5.6-sol`, `anthropic/claude-sonnet-5`, or `google/gemini-3.5-flash`. Models must be enabled on the [Models page](/docs/ai-gateway/using-the-router) before they can be called. Requests route to the model provider, so provider credentials are required: connect [BYOK](/docs/ai-gateway/providers-overview) keys for direct billing and rate-limit ownership, or use **Orq.ai**-managed access via [Credits](/docs/ai-studio/organization/credits) where available. See [Billing & Usage](/docs/ai-studio/organization/billing-usage). Add models from private or self-hosted endpoints to the workspace model catalog. See [Private Models](/docs/ai-gateway/private-models). The [Models page](/docs/ai-gateway/using-the-router) lists the **Context Length** and **Max Output Tokens** columns for every model and can filter by context window size. The static [Supported Models](/docs/ai-gateway/supported-models) page does not list context windows or max output tokens. Claude and most chat models require the conversation to include a user message. Requests whose input is only system or developer instructions, or empty, are rejected with a `400 invalid_input` error. Tool results are grouped into user turns automatically. Ranges are per model: Anthropic models accept `0` to `1`, OpenAI models `0` to `2`. Newer Anthropic models reject requests that set `temperature` and `top_p` together; set one or the other. Audio endpoints accept `0` to `1`. See the [Anthropic provider page](/docs/ai-studio/integrations/providers/anthropic) and [Reasoning](/docs/ai-gateway/features/reasoning). Each provider exposes reasoning differently: OpenAI uses `reasoning_effort`, while Google Gemini and Anthropic use a `thinking` object. The **AI Gateway** drops `temperature` and `top_p` whenever an OpenAI reasoning model reasons, since those parameters are incompatible with OpenAI reasoning models. That covers any `reasoning_effort` other than `none`, and the case where `reasoning_effort` is not set at all because OpenAI and Azure reasoning models reason by default. Set `reasoning_effort: none` to forward sampling parameters. See [Reasoning](/docs/ai-gateway/features/reasoning). Model providers reject incompatible parameter combinations, for example the Anthropic error `4028` when `temperature` and `top_p` are both specified. See [Reasoning](/docs/ai-gateway/features/reasoning) for the parameters the gateway drops on reasoning models. Native file attachment works only with models that support file input directly. For Deployments, file input is supported on OpenAI, Anthropic, and Google Gemini models. PDF support varies by model. Use the [routing rule](/docs/ai-gateway/configuration/routing-rules) "Keep file attachment requests on models that support them" to route by capability. See [Multimodal](/docs/ai-gateway/features/multimodal) and [Files](/docs/ai-studio/ai-engineering/files). Image, PDF, and audio input support varies by model, and image support does not imply PDF support. See [Sending files to models](/docs/ai-gateway/features/files) for the capability table and [Multimodal](/docs/ai-gateway/features/multimodal) for examples. The modality tabs on the [Models page](/docs/ai-gateway/using-the-router) filter by model type, not by accepted input. Models that support structured outputs can return valid JSON matching a schema. Not all models support schemas; check the **JSON Mode** capability filter on the [Models page](/docs/ai-gateway/using-the-router) and use JSON mode as a fallback. See [Structured Outputs](/docs/ai-gateway/features/structured-outputs). All models support streaming, with per-endpoint constraints. See [Streaming](/docs/ai-gateway/features/streaming). Tool calling is supported by most chat models, with format differences per provider. See [Tool Calling](/docs/ai-gateway/features/tool-calling). Prompt caching support varies by provider: Anthropic models use `cache_control` markers, Google Gemini models cache implicitly, and OpenAI models cache automatically. See [Prompt Caching](/docs/ai-gateway/features/prompt-caching). The [Smart Router](/docs/ai-gateway/smart-router) selects the optimal model from a pool per request based on task complexity and the chosen mode. For manual selection, compare models on the [Models page](/docs/ai-gateway/using-the-router). Configure [fallbacks and retries](/docs/ai-gateway/features/retries) or [load balancing](/docs/ai-gateway/features/load-balancing) so requests flow to another model automatically when the primary is unavailable. # Multi-tenant setup Source: https://docs.orq.ai/docs/ai-gateway/multi-tenant-setup Isolate and scope AI Gateway usage per tenant with Identities or request metadata, and apply budgets, routing, and observability per tenant. When one **AI Gateway** deployment serves several customers, teams, or products, each tenant's requests must be tracked and isolated so usage, cost, and traces can be reported per tenant. **Orq.ai** offers two request-level mechanisms for this: * **Identities**, which represent a tenant or its end users on each request. * **Request metadata**, which tags a call with a tenant ID or other key-value data. This guide covers both approaches, when to use each, and a worked example that routes, budgets, and observes a single tenant end to end. For isolating the data those requests retrieve (for example knowledge bases per tenant), see [Isolating data and knowledge bases per tenant](#isolating-data-and-knowledge-bases-per-tenant). ## Which approach to use Choose the approach by how fixed each tenant is and by what needs to be enforced or reported. Use an **Identity** for tenants or end users that are known and stable, when cost and usage must be metered, capped, and reported per identity. Use **metadata** when requests only need a tag to filter on later, and the tenant isn't something to budget against. | | **Identity per tenant** | **Request metadata** | | ---------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | Represents | A known, addressable tenant or end user | Arbitrary key-value context on a request | | Carrier | `identity` object or `X-ORQ-IDENTITY-ID` header | `metadata` object or `X-ORQ-METADATA-*` headers | | Cost & usage reporting | Grouped and filtered per identity | Not a [Reporting](/docs/ai-studio/observability/reporting-api) dimension; filter traces only | | Budgets | Per-identity budget caps | Not a budget scope | | Traces & analytics | Filtered by Identity | Filtered by `metadata.` | | Best for | Tenant billing, per-tenant budgets, end-user attribution | Tenants that don't map cleanly to a fixed identity, ad hoc tagging | ## Approach 1: Identity per tenant An [**Identity**](/docs/ai-studio/observability/identities) represents a **User**, **Team**, **Project**, or **Client**. Create one identity per tenant and attach it to every request that tenant makes. Usage, cost, and traces then attribute to the identity, enabling per-tenant reporting, per-identity budgets, and trace filtering. Create an identity for each tenant once: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --location 'https://my.orq.ai/v2/identities' \ --header "Authorization: Bearer $ORQ_API_KEY" \ --header 'Content-Type: application/json' \ --data-raw '{ "external_id": "tenant-acme", "display_name": "Acme Corp", "metadata": { "plan": "enterprise" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_API_KEY ?? "" }); const identity = await orq.identities.create({ externalId: "tenant-acme", displayName: "Acme Corp", metadata: { plan: "enterprise" }, }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.getenv("ORQ_API_KEY")) identity = orq.identities.create( external_id="tenant-acme", display_name="Acme Corp", metadata={"plan": "enterprise"}, ) ``` Then pass the identity on each request, in the body or as a header: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Help me resolve a billing issue", "name": "SupportAssistant-Production", "identity": { "id": "tenant-acme" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Help me resolve a billing issue", name: "SupportAssistant-Production", identity: { id: "tenant-acme" }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Help me resolve a billing issue", extra_body={ "name": "SupportAssistant-Production", "identity": {"id": "tenant-acme"}, }, ) print(response.output_text) ``` Identity management uses the **Orq** SDK (`orq.identities.create`); requests run through the OpenAI-compatible SDK (`openai` client) or the `/v3/router` endpoints, which accept the `identity` and `metadata` fields on the request body. To track both a tenant and the person acting for it, give each end user an identity ID such as `tenant-{orgId}-{userId}`, or create one identity per tenant and share it across that tenant's users. See [Attaching an identity to a request](/docs/ai-studio/observability/identities#attaching-an-identity-to-a-request) for the body, header, and API-key-owner sources, in the order the gateway checks them. ## Approach 2: Request metadata For tenants that don't map to a fixed set of identities, tag requests with [**request metadata**](/docs/ai-gateway/request-metadata). Metadata adds key-value context such as `tenant_id`, `region`, or `tier`, which appears on traces as `metadata.` span attributes and can be used as a trace filter. Pass a `metadata` object on the request: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Summarize my contracts", "metadata": { "tenant_id": "acme", "region": "eu-west-1" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Summarize my contracts", metadata: { tenant_id: "acme", region: "eu-west-1" }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Summarize my contracts", extra_body={"metadata": {"tenant_id": "acme", "region": "eu-west-1"}}, ) print(response.output_text) ``` When the request body cannot be modified, set the metadata with headers instead. Each `X-ORQ-METADATA-` header sets one metadata key, lowercased from the header suffix: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -H "X-ORQ-METADATA-TENANT_ID: acme" \ -H "X-ORQ-METADATA-REGION: eu-west-1" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Summarize my contracts" }' ``` Metadata feeds **routing rules**, **guardrail rules**, and **budget matching**, and appears as a trace filter, so requests can be routed, guarded, and inspected per tenant without an identity. Metadata is **not** a [Reporting](/docs/ai-studio/observability/reporting-api) dimension or a budget scope. To aggregate cost per tenant or cap a tenant's spend, use the identity or project scope instead. Keep metadata to a small, fixed set of low-cardinality keys such as `tenant_id`, `region`, and `tier`. High-cardinality values, such as per-request IDs, defeat filtering and increase storage. See [Request metadata best practices](/docs/ai-gateway/request-metadata#best-practices). ## Identity and metadata together Use an **Identity** to meter and budget a tenant; use **metadata** for extra context such as region or product line. The two work together: attach the tenant's **Identity** for per-tenant attribution and budgets, then add **metadata** to describe the request. ## Worked example: one tenant, end to end This example sets up **Acme Corp** as a tenant with per-tenant routing, a monthly budget, and per-tenant observability, using an identity plus a routing rule keyed on identity and metadata. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --location 'https://my.orq.ai/v2/identities' \ --header "Authorization: Bearer $ORQ_API_KEY" \ --header 'Content-Type: application/json' \ --data-raw '{ "external_id": "tenant-acme", "display_name": "Acme Corp" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_API_KEY ?? "" }); const identity = await orq.identities.create({ externalId: "tenant-acme", displayName: "Acme Corp", }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.getenv("ORQ_API_KEY")) identity = orq.identities.create( external_id="tenant-acme", display_name="Acme Corp", ) ``` Navigate to **Settings > Organization > Budgets** and click **Create**. Set **Scope > Applies to** to **Identity**, select the `tenant-acme` identity, and set a monthly **Cost** limit. When the limit is reached, requests matching Acme's identity are rejected until the monthly reset, while other tenants keep working. See [Budgets](/docs/ai-gateway/budgets) for period limits and alerts. Anyone with a key can set an **Identity** on a request; the gateway does not check that the key owns that identity. So identity-based budgets and cost reporting are only as trustworthy as the keys that can reach the gateway. Give keys only to trusted backends. To cap spend for untrusted tenants, use a per-tenant key or per-tenant project instead of identity matching. On each request, attach the identity and a metadata key so routing and trace filters can target Acme: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "agent/support-bot", "input": "Reset my password", "identity": { "id": "tenant-acme" }, "metadata": { "tenant_id": "acme" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "agent/support-bot", input: "Reset my password", identity: { id: "tenant-acme" }, metadata: { tenant_id: "acme" }, }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="agent/support-bot", input="Reset my password", extra_body={ "identity": {"id": "tenant-acme"}, "metadata": {"tenant_id": "acme"}, }, ) ``` Create a [Routing Rule](/docs/ai-gateway/configuration/routing-rules) whose condition matches the **Identity** `tenant-acme` to send Acme's traffic to a dedicated model or variant. Combine conditions on identity and metadata (for example region) with the rule builder. Query the [Reporting API](/docs/ai-studio/observability/reporting-api), filtering to the tenant identity: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} FROM=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "1 day ago" +%Y-%m-%dT%H:%M:%SZ) TO=$(date -u +%Y-%m-%dT%H:%M:%SZ) curl -X POST "https://my.orq.ai/v2/reporting" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"metric\": \"genai.usage\",\"from\": \"$FROM\",\"to\": \"$TO\",\"grain\": \"day\",\"group_by\": [\"identity\"],\"filters\": [{\"field\": \"identity\",\"op\": \"eq\",\"values\": [\"tenant-acme\"]}]}" ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq from datetime import datetime, timedelta, timezone import os orq = Orq(api_key=os.getenv("ORQ_API_KEY")) result = orq.reporting.query( metric="genai.usage", from_=datetime.now(timezone.utc) - timedelta(days=1), to=datetime.now(timezone.utc), grain="day", group_by=["identity"], filters=[{"field": "identity", "op": "eq", "values": ["tenant-acme"]}], ) ``` In the **Traces** view, filter by **Identity** `tenant-acme`, or by **Metadata** `tenant_id: acme`, to inspect Acme's latency, token usage, and errors. ## Isolating data and knowledge bases per tenant Request-level attribution meters usage, but it does not separate the data a tenant can retrieve. To isolate content, put each tenant in its own [**Project**](/docs/ai-studio/get-started/projects): * **Projects are the isolation boundary.** A **Project** holds Deployments, Prompts, Agents, Knowledge Bases, and Datasets, with project-scoped observability and budgets. * **Knowledge bases follow the project.** A knowledge base lives under a `project/path` and is retrieved through a Deployment in that project. With one project per tenant, a tenant's key cannot reach another tenant's knowledge base. * **Single-project keys stay inside the tenant.** Only keys created with **single-project** scope are confined to that tenant's project. Workspace-wide and legacy keys can reach every project, so issue single-project keys to tenants and never hand out a workspace-wide key. * **Or share one knowledge base among trusted tenants.** If tenants trust one another, tag chunks with a `client_id` metadata field and add a `filter_by` condition to narrow retrieval to that tenant. This is a query-time filter, not enforced access control: any caller can omit it and read every tenant's chunks. Use it only among trusted tenants; otherwise separate tenants into per-tenant projects. See [Chunk Metadata in Knowledge Bases](/docs/ai-studio/ai-engineering/knowledge-bases). For a full setup: one **project per tenant** for data isolation and per-tenant budgets, an **Identity** (or the project-scoped key) so usage reports and traces attribute to the tenant, and **metadata** for extra context such as region or product line. ## See also Create identities and attach them to requests for per-tenant attribution and budgets. Attach name, identity, thread, tags, and custom metadata to AI Gateway requests. Cap spend per workspace, project, identity, API key, provider, or model. Query per-tenant usage, cost, and performance programmatically. # Bring Your Own Model Source: https://docs.orq.ai/docs/ai-gateway/private-models Connect fine-tuned, self-hosted, and privately deployed models from Azure AI Foundry, Google Vertex AI, or LiteLLM to the AI Gateway. Use private models alongside public models through the same AI Gateway endpoint. Private models are useful when you have fine-tuned models, dedicated deployments, or self-hosted inference that should remain under your infrastructure and provider contracts. ## Supported private model sources Import OpenAI-compatible and publisher model deployments from an Azure AI Foundry project. Connect models deployed in your Google Cloud project using a Vertex AI service account. Import self-hosted or custom models exposed through your LiteLLM instance. ## Add a private model In the AI Gateway sidebar, open **Models** and select **Model**. Select Azure AI Foundry, Vertex AI, or LiteLLM and enter the requested endpoint and credentials. Import the available deployments, then enable each model that should be available through the AI Gateway. Reference an imported model as `@/`. ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} corp@azure/gpt-5.6-sol ``` Enabled private models are available for requests through [routing rules](/docs/ai-gateway/configuration/routing-rules). Enable models, filter by capability and region, and control model access by project. # BYOK Source: https://docs.orq.ai/docs/ai-gateway/providers-overview Connect OpenAI, Anthropic, Google, AWS, and 30+ providers to the AI Gateway using API keys. **BYOK** (Bring Your Own Key) connects provider API keys directly to the **AI Gateway**. Every request is routed through those credentials, keeping billing, rate limits, and data residency under the organization's control. ## Connecting a provider key 1. Go to **BYOK** in the **AI Gateway** sidebar. 2. Find the provider and click Connect. 3. Select Setup your own API key and paste the key. Providers configuration screen showing provider API key setup options. View the full list of providers available in the **AI Gateway**, including OpenAI, Anthropic, Google, AWS Bedrock, Azure, and more. ## Multiple keys per provider Add more than one key per provider by selecting Add a new API key from the provider panel. ### Why use multiple keys **Failover**: if one key is revoked or exhausted, a fallback model configured with a second key keeps requests flowing. **Environment separation**: use different keys for production, staging, and development without mixing spend. ## Why use your own keys For production workloads, BYOK provides: * **Rate limit ownership**: provider rate limits apply to the organization's account, not a shared pool * **Direct billing**: spend is billed by the provider directly; configure limits and alerts independently * **Data residency**: requests go from the **AI Gateway** to the provider account with no shared credential layer # Request metadata Source: https://docs.orq.ai/docs/ai-gateway/request-metadata Attach app name, identity, thread, tags, and custom metadata to AI Gateway requests. **Use Cases** Name each request so cost and performance slice by product, feature, or environment. Attach an **Identity** so spend, latency, and error rates attribute to a user, team, or client, with optional per-identity budgets. Tag each turn with a **Thread** ID so the full conversation groups together in observability. Attach key-value metadata, such as tier, channel, or feature flag, and filter traces by those fields. ## Overview Every **AI Gateway** request can carry context through several mechanisms: * `name`: marks the app or service * `identity`: marks the end user or tenant * `thread`: groups a conversation * `metadata`: carries business context * `tags`: adds grouping labels Each mechanism answers one question and surfaces through its own channel. Use the decision table below to pick the mechanism for a given question. `variables` also travel with the request, but fill prompt templates instead of describing the request. The how-to for each mechanism lives in [App Tracking](/docs/ai-gateway/app-tracking), [Identities](/docs/ai-studio/observability/identities), and [Thread Management](/docs/ai-gateway/thread-management); the span attribute reference is in [Metadata](/docs/ai-studio/observability/span-attributes). ## Which mechanism to use | Mechanism | Answers the question | Use when | | ---------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `name` | Which app, service, or feature made this call? | Cost and performance per product line, with a fixed set of known surfaces | | `identity` | Which end user, team, or client is this request for? | Per-user attribution, tenant billing, per-identity budgets | | `thread` | Which conversation or workflow does this request belong to? | Multi-turn chats, multi-step agent workflows, support tickets | | `metadata` | What business context applies to this request? | Key-value slicing by tier, channel, region, or feature flag | | `tags` | Which labels group these requests? | Grouping across apps, users, or conversations, for example `support`, `premium`, `experiment-a` | ## Quick Start Send one request with all five mechanisms attached. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Refund my order", "name": "SupportAssistant-Production", "metadata": { "customer_tier": "premium", "channel": "email" }, "tags": ["support", "refund"], "thread": { "id": "conversation-abc123", "tags": ["user-123"] }, "identity": { "id": "user_123" } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.4-mini", input: "Refund my order", name: "SupportAssistant-Production", metadata: { customer_tier: "premium", channel: "email" }, tags: ["support", "refund"], thread: { id: "conversation-abc123", tags: ["user-123"] }, identity: { id: "user_123" }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.4-mini", input="Refund my order", extra_body={ "name": "SupportAssistant-Production", "metadata": {"customer_tier": "premium", "channel": "email"}, "tags": ["support", "refund"], "thread": {"id": "conversation-abc123", "tags": ["user-123"]}, "identity": {"id": "user_123"}, }, ) print(response.output_text) ``` ## Configuration | Parameter | Type | Trace filter | Description | | ----------- | --------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Name | Display name on the trace. Recommended: alphanumeric and hyphens only, under 50 characters, no timestamps or dynamic values. | | `metadata` | object | `metadata.` | Key-value pairs with string values. On the **Responses API**, non-string values are rejected with a 400. | | `tags` | string\[] | Via `orq.tags` span attribute | Labels for filtering and reporting. | | `thread` | object | Thread ID | Groups related requests: `id` (required) plus optional `tags`. | | `identity` | object | Identity | Attributes the request to an end user: `id` (required) plus optional `display_name`, `email`, `metadata`, and `tags`. | | `variables` | object | — | Template variables for prompt substitution. Pass secrets as `{"secret": true, "value": "..."}` so they are redacted from traces. | On `/v3/router/chat/completions`: * `metadata` is limited to 16 key-value pairs with keys up to 64 characters and values up to 512 characters * `thread`, `identity`, and `tags` are passed under the `orq` object (`orq.thread`, `orq.identity`, `orq.tags`) * `name` is passed at the top level ## Headers Clients that cannot modify the request body, such as coding agents, attach metadata, identity, and thread context through headers instead. `X-ORQ-IDENTITY-ID` and `X-ORQ-THREAD-ID` are read on every AI Gateway request. `X-ORQ-METADATA-` and `X-ORQ-METADATA` are read on the inference endpoints: `/v3/router/responses`, `/v3/anthropic/v1/messages`, `/v3/google/v1beta/models/*` and `/v3/google/v1beta/interactions`, and the `/v3/router/*` completions, embeddings, image, audio, moderation, OCR, and rerank endpoints. | Header | Sets | Example | | ---------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | | `X-ORQ-METADATA-` | One metadata key per header. The key is the lowercased header suffix. | `X-ORQ-METADATA-REPO: acme-api` sets `metadata.repo` to `acme-api`. | | `X-ORQ-METADATA` | Several metadata keys in one header: a comma-separated `key=value` list, or a JSON object. | `X-ORQ-METADATA: repo=acme-api,ticket=PROJ-123` | | `X-ORQ-IDENTITY-ID` | The identity ID. See [Identities](/docs/ai-studio/observability/identities). | `X-ORQ-IDENTITY-ID: user_123` | | `X-ORQ-THREAD-ID` | The thread ID. | `X-ORQ-THREAD-ID: conversation-abc123` | When the `X-ORQ-METADATA` value starts with `{`, it is parsed as a JSON object instead of the comma-separated form. String, number, and boolean values are kept; object, array, and null values are skipped. On the Anthropic Messages and Google endpoints only, a fixed allowlist of headers (`user-agent`, `originator`, `session-id`, `session_id`, `thread-id`, `x-app`, `x-claude-code-session-id`, `x-codex-beta-features`, `anthropic-beta`, `anthropic-version`, `anthropic-dangerous-direct-browser-access`) is captured into metadata automatically, when present, to identify the calling coding assistant. No other endpoint captures these headers automatically. Precedence when the same metadata key is set more than once: the body `metadata` object wins, then `X-ORQ-METADATA-` headers, then the `X-ORQ-METADATA` header, then, on the Anthropic Messages and Google endpoints only, the automatically-captured allowlist above. **Limits**: up to 20 metadata keys per request from `X-ORQ-METADATA` and `X-ORQ-METADATA-` combined. On those endpoints, the automatically-captured allowlist headers do not count against this limit. Keys must be 64 characters or fewer and match `[a-z0-9._-]+`. Values longer than 256 characters are truncated. Entries that fail these rules are dropped silently; the request still succeeds. Header-derived metadata reaches [Traces](/docs/ai-studio/observability/traces) as `metadata.` span attributes on every endpoint listed above, filterable the same way as body metadata. It is never forwarded to the model provider. On endpoints that take a JSON body, `X-ORQ-METADATA` and `X-ORQ-METADATA-` are also available to routing rules, guardrail rules, and budgets, so a caller able to set headers on a request can influence which of those rules match. Endpoints that take multipart uploads (transcription, translation, image edit, image variation) put header metadata on traces only. The automatically-captured allowlist above never reaches rule matching, on any endpoint. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -H "X-ORQ-METADATA-REPO: acme-api" \ -H "X-ORQ-METADATA-TICKET: PROJ-123" \ -H "X-ORQ-THREAD-ID: conversation-abc123" \ -d '{ "model": "openai/gpt-5.4-mini", "input": "Refund my order" }' ``` ## Best Practices * **Keep app names low-cardinality**: Use a small fixed set of app names (around 50 per workspace) with consistent patterns such as `Service-Environment`. Avoid timestamps or dynamic values, which fragment analytics. * **Use a fixed metadata key set**: Define a small set of keys (`customer_tier`, `channel`, `region`) and reuse them. High-cardinality keys, such as request IDs or timestamps, defeat filtering and increase storage. * **Thread IDs**: Use UUIDs or composite keys such as `user-{userId}-{sessionId}` to avoid collisions across sessions. * **Identity IDs**: Use predictable patterns such as `user-{userId}` or `tenant-{tenantId}` so identities stay consistent across requests. * **One mechanism per question**: If the value describes the app, use `name`; if it describes the user, use `identity`; if it is business context, use `metadata`. ## What not to store in request metadata * **PII**: Do not put emails, phone numbers, or personal data in `metadata`, `name`, or `tags`; they persist on stored traces. To keep sensitive values out of stored traces, include `"metadata"` in [`security.mask`](/docs/ai-gateway/features/security), or enable [PII Redaction](/docs/ai-gateway/features/plugins/pii-redaction). * **Secrets**: Pass tokens and keys as template variables with `{"secret": true, "value": "..."}` so they are redacted from traces. See [Run Agents](/docs/ai-studio/ai-engineering/run-agents) for the variable reference. # Smart Router Source: https://docs.orq.ai/docs/ai-gateway/smart-router Automatically route each request to the optimal model in a pool based on task complexity and the chosen mode. Reduce costs without sacrificing quality. Not every request needs the most capable model. The **Smart Router** routes each request to the optimal model from a configured pool, based on the complexity of the task and the chosen mode. Instead of pinning an application to a single model, configure a pool of 2 to 50 models and let the router decide per request. Simple requests go to economical models; complex requests escalate to stronger ones. The application calls one stable model reference; the routing happens behind it. ## Use Cases * Reducing spend on high-volume workloads where most requests do not need the strongest model. * Customer support assistants: simple FAQs and acknowledgements stay on fast, economical models while nuanced complaints or policy questions escalate automatically. * Code assistants: autocomplete and boilerplate generation stay cheap; debugging and architecture questions escalate. * Content generation at scale: templated, high-volume copy uses economical models; long-form or brand-sensitive copy uses stronger ones. * Getting the best available output for every request without manually picking a model per call. To choose which models belong in the pool, [**Model Arena**](/docs/ai-gateway/model-arena) ranks candidates head-to-head on real prompts from the workload and maps each rating against its cost. ## How It Works Every eligible model carries an **Intelligence Index** sourced from [Artificial Analysis](https://artificialanalysis.ai). The **Smart Router** ranks the pool by this index and groups the models into complexity bands: **Easy**, **Medium**, and **Hard**. Each incoming request is analyzed for task complexity and routed to a model in the matching band, so the bands adapt automatically to whichever models are in the pool. The selected mode (**Cost**, **Balanced**, or **Quality**) tunes how aggressively the router prefers economical models over stronger ones. If routing is unavailable, the request falls back to the strongest model in the pool, so requests always complete. ## Set Up the Smart Router 1. Navigate to **Smart Router** in the **AI Gateway** sidebar. 2. Click + Smart Router. 3. Fill in the configuration: * **Name**: a unique identifier, used as the stable model key in **Gateway** requests (lowercase letters, numbers, and hyphens only). The name cannot be changed after creation. * **Smart router mode**: choose **Cost**, **Balanced**, or **Quality**. * **Models**: click Add model and select at least 2 models (up to 50); keeping the pool below 10 models is recommended. Each entry shows the model's Intelligence Index and price, and the list groups the selection into the **Easy**, **Medium**, and **Hard** bands as models are added. See [Understanding the Model Price](#understanding-the-model-price) for what the price figure represents. 4. Click **Create**. Create Smart Router sheet with a name, the mode toggle set to Cost, and a pool of three models grouped into the Hard, Medium, and Easy bands, with the model picker open ## Understanding the Model Price Each model row in the **Smart Router** form shows an Intelligence Index and a price. The price is a static catalog figure for comparing models in the pool: * **Basis**: a blended input and output rate in USD per 1M tokens, weighted 3:1 toward input (`(3 × input price + output price) ÷ 4`) * **Source**: [Artificial Analysis](https://artificialanalysis.ai) pricing data * **Included**: the list price of input and output tokens only * **Excluded**: prompt cache reads and writes, separately billed reasoning or thinking tokens, web search fees, extended-context and service-tier pricing, batch rates, provider discounts, and costs added by **Orq.ai** * **Precision**: displayed in USD with two decimal places The price does not select the model: the **Smart Router** splits the pool into **Easy**, **Medium**, and **Hard** bands by Intelligence Index and routes each request by task complexity and mode. For per-token list prices, see the [Models page](/docs/ai-gateway/using-the-router) in **AI Gateway**, which shows input and output rates separately. Actual spend is not derived from this figure. It is computed per request from real token usage and provider rates, and appears in the observability [Traces](/docs/ai-studio/observability/traces) under the `orq.billing.*` attributes, in [Billing & Usage](/docs/ai-studio/organization/billing-usage), and in the [Reporting API](/docs/ai-studio/observability/reporting-api). ## Modes | Mode | Behavior | | ------------ | ------------------------------------------------------------------------- | | **Cost** | Prefers the economical models in the pool more aggressively to save money | | **Balanced** | Balances cost and quality across simple and complex requests | | **Quality** | Routes more requests to the stronger models in the pool | New **Smart Routers** default to **Cost**. ## Eligible Models Any enabled model in the **AI Gateway** that carries Artificial Analysis intelligence data can join the pool, and models from different providers can be mixed freely in a single pool. See [Supported Models](/docs/ai-gateway/supported-models) for the catalog. Models without an Intelligence Index and other **Smart Routers** cannot be added. Ensure the models intended for the pool are enabled in the Model list first. Disabled models do not appear in the model picker. ## Use the Smart Router Once created, the **Smart Router** appears in **AI Gateway** and can be referenced anywhere a model is accepted via the API or SDKs. **Smart Routers** are not yet available for **Agents**: routing happens per request, so it cannot yet be guaranteed that an entire conversation is served by the same model. Support for **Agents** will be added soon. Smart Router list page showing three routers with their profile, pool model providers, and enabled status ### Reference in code When using a **Smart Router** through the SDKs, API, or [Supported Libraries](/docs/ai-studio/integrations/frameworks/overview), reference it by the string `@orq/`. > Example: `acme@orq/my-smart-router` ### Run the Smart Router Call the **Smart Router** like any other model. The router picks a model from the pool per request; no code changes are needed when the pool or mode changes. Both the Responses endpoint and the Chat Completions endpoint accept a **Smart Router** reference. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "workspace_name@orq/my-smart-router", "input": "Summarize this support ticket in two sentences." }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "workspace_name@orq/my-smart-router", input: "Summarize this support ticket in two sentences.", }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="workspace_name@orq/my-smart-router", input="Summarize this support ticket in two sentences.", ) print(response.output_text) ``` Disabling a **Smart Router** makes it unavailable to the **Gateway**; requests referencing it no longer resolve until it is enabled again. ## Track Usage Requests served by a **Smart Router** appear in the observability [Logs](/docs/ai-studio/observability/logs) like any other **Gateway** request. Use [App Tracking](/docs/ai-gateway/app-tracking) to attribute cost and latency per application. # Supported models in AI Gateway Source: https://docs.orq.ai/docs/ai-gateway/supported-models Browse LLM models available through the AI Gateway. Access GPT, Claude, Gemini, and 500+ models from top providers with unified API integration. ## List of supported models ### Responses API ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl "https://my.orq.ai/v3/router/responses" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Write a one-sentence bedtime story about a unicorn." }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) response = client.responses.create( model="openai/gpt-5.6-sol", input="Write a one-sentence bedtime story about a unicorn." ) print(response.output_text) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Write a one-sentence bedtime story about a unicorn." }); console.log(response.output_text); } main(); ``` #### Supported Models | Provider | Model | | ----------- | ------------------------------------------------------ | | Alibaba | `alibaba/deepseek-v4-flash` | | Alibaba | `alibaba/deepseek-v4-pro` | | Alibaba | `alibaba/kimi-k2.5` | | Alibaba | `alibaba/kimi-k2.6` | | Alibaba | `alibaba/qwen-mt-flash` | | Alibaba | `alibaba/qwen-mt-lite` | | Alibaba | `alibaba/qwen-plus` | | Alibaba | `alibaba/qwen-plus-2025-12-01` | | Alibaba | `alibaba/qwen3-max` | | Alibaba | `alibaba/qwen3-vl-flash` | | Alibaba | `alibaba/qwen3-vl-flash-2025-10-15` | | Alibaba | `alibaba/qwen3-vl-plus` | | Alibaba | `alibaba/qwen3.5-122b-a10b` | | Alibaba | `alibaba/qwen3.5-27b` | | Alibaba | `alibaba/qwen3.5-35b-a3b` | | Alibaba | `alibaba/qwen3.5-flash` | | Alibaba | `alibaba/qwen3.5-flash-2026-02-23` | | Alibaba | `alibaba/qwen3.6-35b-a3b` | | Alibaba | `alibaba/qwen3.6-flash` | | Alibaba | `alibaba/qwen3.6-flash-2026-04-16` | | Alibaba | `alibaba/qwen3.6-plus` | | Alibaba | `alibaba/qwen3.6-plus-2026-04-02` | | Alibaba | `alibaba/qwen3.7-max` | | Alibaba | `alibaba/qwen3.7-max-2026-05-20` | | Alibaba | `alibaba/qwen3.7-max-2026-06-08` | | Alibaba | `alibaba/qwen3.7-plus` | | Alibaba | `alibaba/qwen3.7-plus-2026-05-26` | | Alibaba | `alibaba/qwen3.8-max` | | Anthropic | `anthropic/claude-fable-5` | | Anthropic | `anthropic/claude-fable-5-1` | | Anthropic | `anthropic/claude-haiku-4-5` | | Anthropic | `anthropic/claude-haiku-4-5-20251001` | | Anthropic | `anthropic/claude-opus-4-5` | | Anthropic | `anthropic/claude-opus-4-5-20251101` | | Anthropic | `anthropic/claude-opus-4-6` | | Anthropic | `anthropic/claude-opus-4-7` | | Anthropic | `anthropic/claude-opus-4-8` | | Anthropic | `anthropic/claude-opus-5` | | Anthropic | `anthropic/claude-sonnet-4-5` | | Anthropic | `anthropic/claude-sonnet-4-5-20250929` | | Anthropic | `anthropic/claude-sonnet-4-6` | | Anthropic | `anthropic/claude-sonnet-5` | | AWS Bedrock | `aws/ai21.jamba-1-5-mini-v1:0` | | AWS Bedrock | `aws/apac.amazon.nova-lite-v1:0` | | AWS Bedrock | `aws/apac.amazon.nova-micro-v1:0` | | AWS Bedrock | `aws/apac.amazon.nova-pro-v1:0` | | AWS Bedrock | `aws/apac.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/au.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/au.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/deepseek.v3.2` | | AWS Bedrock | `aws/deepseek.v3.2-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-lite-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-micro-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-pro-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-fable-5` | | AWS Bedrock | `aws/eu.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-5-20251101-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-6-v1` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-7` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-8` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-5` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-5` | | AWS Bedrock | `aws/global.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-fable-5` | | AWS Bedrock | `aws/global.anthropic.claude-fable-5-1` | | AWS Bedrock | `aws/global.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-5-20251101-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-6-v1` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-7` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-8` | | AWS Bedrock | `aws/global.anthropic.claude-opus-5` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-5` | | AWS Bedrock | `aws/google.gemma-3-27b-it` | | AWS Bedrock | `aws/jp.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/jp.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/jp.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/jp.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/meta.llama3-1-405b-instruct-v1:0` | | AWS Bedrock | `aws/minimax.minimax-m2.1` | | AWS Bedrock | `aws/minimax.minimax-m2.5` | | AWS Bedrock | `aws/mistral.devstral-2-123b` | | AWS Bedrock | `aws/mistral.magistral-small-2509` | | AWS Bedrock | `aws/mistral.ministral-3-3b-instruct` | | AWS Bedrock | `aws/mistral.mistral-large-3-675b-instruct` | | AWS Bedrock | `aws/mistral.pixtral-large-2502-v1:0` | | AWS Bedrock | `aws/moonshotai.kimi-k2.5` | | AWS Bedrock | `aws/nvidia.nemotron-nano-3-30b` | | AWS Bedrock | `aws/nvidia.nemotron-super-3-120b` | | AWS Bedrock | `aws/openai.gpt-oss-120b-1:0` | | AWS Bedrock | `aws/qwen.qwen3-32b-v1:0` | | AWS Bedrock | `aws/qwen.qwen3-coder-next` | | AWS Bedrock | `aws/us.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/us.amazon.nova-lite-v1:0` | | AWS Bedrock | `aws/us.amazon.nova-micro-v1:0` | | AWS Bedrock | `aws/us.amazon.nova-pro-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-fable-5` | | AWS Bedrock | `aws/us.anthropic.claude-fable-5-1` | | AWS Bedrock | `aws/us.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-opus-4-1-20250805-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-opus-4-5-20251101-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-opus-4-8` | | AWS Bedrock | `aws/us.anthropic.claude-opus-5` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-5` | | AWS Bedrock | `aws/us.writer.palmyra-x5-v1:0` | | AWS Bedrock | `aws/writer.palmyra-x5-v1:0` | | AWS Bedrock | `aws/zai.glm-4.7` | | AWS Bedrock | `aws/zai.glm-5` | | Azure | `azure/eu.gpt-5.6-luna` | | Azure | `azure/eu.gpt-5.6-terra` | | Azure | `azure/global.gpt-5.6-luna` | | Azure | `azure/global.gpt-5.6-terra` | | Azure | `azure/gpt-4.1` | | Azure | `azure/gpt-4.1-mini` | | Azure | `azure/gpt-4.1-nano` | | Azure | `azure/gpt-4o` | | Azure | `azure/gpt-5` | | Azure | `azure/gpt-5-mini` | | Azure | `azure/gpt-5-nano` | | Azure | `azure/gpt-5.4` | | Azure | `azure/gpt-5.6-sol` | | Azure | `azure/gpt-5.6-sol-global` | | baseten | `baseten/deepseek-v4-flash-0731` | | baseten | `baseten/deepseek-v4-pro` | | baseten | `baseten/deepseek-v4-pro-0813` | | baseten | `baseten/glm-4.7` | | baseten | `baseten/glm-5.2` | | baseten | `baseten/glm-5.2-fast` | | baseten | `baseten/glm-5.3` | | baseten | `baseten/glm-5.3-flash` | | baseten | `baseten/gpt-oss-120b` | | baseten | `baseten/inkling` | | baseten | `baseten/inkling-small` | | baseten | `baseten/kimi-k2.6` | | baseten | `baseten/kimi-k2.7-code` | | baseten | `baseten/kimi-k3` | | baseten | `baseten/nemotron-ultra` | | Cerebras | `cerebras/gemma-4-31b` | | Cerebras | `cerebras/gpt-oss-120b` | | Cohere | `cohere/c4ai-aya-expanse-32b` | | Cohere | `cohere/c4ai-aya-vision-32b` | | Cohere | `cohere/command-a-03-2025` | | Cohere | `cohere/command-a-plus-05-2026` | | Cohere | `cohere/command-a-reasoning-08-2025` | | Cohere | `cohere/command-a-translate-08-2025` | | Cohere | `cohere/command-a-vision-07-2025` | | Cohere | `cohere/command-r-08-2024` | | Cohere | `cohere/command-r-plus-08-2024` | | Cohere | `cohere/command-r7b-12-2024` | | Cohere | `cohere/command-r7b-arabic-02-2025` | | Deepseek | `deepseek/deepseek-v4-flash` | | Deepseek | `deepseek/deepseek-v4-pro` | | fireworks | `fireworks/deepseek-v4-pro` | | fireworks | `fireworks/glm-5.2` | | fireworks | `fireworks/kimi-k2.7-code` | | fireworks | `fireworks/kimi-k3` | | fireworks | `fireworks/kimi-k3-fast` | | fireworks | `fireworks/minimax-m3` | | fireworks | `fireworks/qwen3.7-plus` | | Vertex AI | `google/claude-haiku-4-5` | | Vertex AI | `google/claude-opus-4-5@20251101` | | Vertex AI | `google/claude-opus-4-6` | | Vertex AI | `google/claude-opus-4-7` | | Vertex AI | `google/claude-opus-4-8` | | Vertex AI | `google/claude-sonnet-4-5@20250929` | | Vertex AI | `google/claude-sonnet-4-6` | | Vertex AI | `google/claude-sonnet-4@20250514` | | Vertex AI | `google/eu.claude-fable-5` | | Vertex AI | `google/eu.claude-fable-5-1` | | Vertex AI | `google/eu.claude-opus-4-7` | | Vertex AI | `google/eu.claude-opus-4-8` | | Vertex AI | `google/eu.claude-opus-5` | | Vertex AI | `google/eu.claude-sonnet-5` | | Vertex AI | `google/eu.gemini-3.1-flash-lite` | | Vertex AI | `google/eu.gemini-3.5-flash` | | Vertex AI | `google/eu.gemini-3.5-flash-lite` | | Vertex AI | `google/eu.gemini-3.6-flash` | | Vertex AI | `google/eu.gemini-3.7-flash` | | Vertex AI | `google/eu.gemini-3.8-flash` | | Vertex AI | `google/gemini-2.5-flash` | | Vertex AI | `google/gemini-2.5-flash-lite` | | Vertex AI | `google/gemini-2.5-pro` | | Vertex AI | `google/gemini-3-flash-preview` | | Vertex AI | `google/gemini-3.1-flash-lite` | | Vertex AI | `google/gemini-3.1-pro-preview` | | Vertex AI | `google/gemini-3.5-flash` | | Vertex AI | `google/gemini-3.5-flash-lite` | | Vertex AI | `google/gemini-3.6-flash` | | Vertex AI | `google/gemini-3.7-flash` | | Vertex AI | `google/gemini-3.8-flash` | | Vertex AI | `google/global.claude-fable-5` | | Vertex AI | `google/global.claude-fable-5-1` | | Vertex AI | `google/global.claude-opus-4-7` | | Vertex AI | `google/global.claude-opus-4-8` | | Vertex AI | `google/global.claude-opus-5` | | Vertex AI | `google/global.claude-sonnet-5` | | Vertex AI | `google/us.claude-fable-5` | | Vertex AI | `google/us.claude-fable-5-1` | | Vertex AI | `google/us.claude-opus-4-7` | | Vertex AI | `google/us.claude-opus-4-8` | | Vertex AI | `google/us.claude-opus-5` | | Vertex AI | `google/us.claude-sonnet-5` | | Vertex AI | `google/us.gemini-3.1-flash-lite` | | Vertex AI | `google/us.gemini-3.5-flash` | | Vertex AI | `google/us.gemini-3.5-flash-lite` | | Vertex AI | `google/us.gemini-3.6-flash` | | Vertex AI | `google/us.gemini-3.7-flash` | | Vertex AI | `google/us.gemini-3.8-flash` | | Google AI | `google-ai/gemini-2.5-flash` | | Google AI | `google-ai/gemini-2.5-flash-lite` | | Google AI | `google-ai/gemini-2.5-pro` | | Google AI | `google-ai/gemini-3-flash-preview` | | Google AI | `google-ai/gemini-3.1-pro-preview` | | Google AI | `google-ai/gemini-3.1-pro-preview-customtools` | | Google AI | `google-ai/gemini-3.5-flash` | | Google AI | `google-ai/gemini-3.5-flash-lite` | | Google AI | `google-ai/gemini-3.6-flash` | | Google AI | `google-ai/gemini-3.7-flash` | | Google AI | `google-ai/gemini-3.8-flash` | | Google AI | `google-ai/gemini-omni-flash-preview` | | Google AI | `google-ai/gemma-4-26b-a4b-it` | | Google AI | `google-ai/gemma-4-31b-it` | | greenpt | `greenpt/deepseek-v4-flash-0731` | | greenpt | `greenpt/gemma4` | | greenpt | `greenpt/glm-5.2` | | greenpt | `greenpt/glm-5.2-caveman` | | greenpt | `greenpt/glm-5.2-caveman-lite` | | greenpt | `greenpt/glm-5.2-caveman-ultra` | | greenpt | `greenpt/glm-5.2-honey` | | greenpt | `greenpt/glm-5.2-honey-lite` | | greenpt | `greenpt/glm-5.2-honey-ultra` | | greenpt | `greenpt/glm-5.2-ponytail` | | greenpt | `greenpt/glm-5.2-ponytail-lite` | | greenpt | `greenpt/glm-5.2-ponytail-ultra` | | greenpt | `greenpt/gpt-oss-120b` | | greenpt | `greenpt/green-l` | | greenpt | `greenpt/green-l-raw` | | greenpt | `greenpt/green-r` | | greenpt | `greenpt/green-r-raw` | | greenpt | `greenpt/kimi-k2.6` | | greenpt | `greenpt/kimi-k2.7-code` | | greenpt | `greenpt/kimi-k3` | | greenpt | `greenpt/llama-3.3-70b-instruct` | | greenpt | `greenpt/minimax-m2.5` | | greenpt | `greenpt/mistral-medium-3.5-128b` | | greenpt | `greenpt/mistral-small-3.2-24b-instruct-2506` | | greenpt | `greenpt/qwen3-235b-a22b-instruct-2507` | | greenpt | `greenpt/qwen3.5-397b-a17b` | | greenpt | `greenpt/qwen3.6-35b-a3b` | | Groq | `groq/allam-2-7b` | | Groq | `groq/groq/compound` | | Groq | `groq/groq/compound-mini` | | Groq | `groq/meta-llama/llama-prompt-guard-2-22m` | | Groq | `groq/meta-llama/llama-prompt-guard-2-86m` | | Groq | `groq/openai/gpt-oss-120b` | | Groq | `groq/openai/gpt-oss-20b` | | Groq | `groq/openai/gpt-oss-safeguard-20b` | | Groq | `groq/qwen/qwen3.6-27b` | | Groq | `groq/qwen/qwen3.8-27b` | | hcompany | `hcompany/holo3-1-35b-a3b` | | hcompany | `hcompany/holo3-122b-a10b` | | inceptron | `inceptron/deepseek-ai/DeepSeek-V4-Flash-0731` | | inceptron | `inceptron/MiniMaxAI/MiniMax-M2.5` | | inceptron | `inceptron/moonshotai/Kimi-K2.6` | | inceptron | `inceptron/moonshotai/Kimi-K2.7-Code` | | inceptron | `inceptron/zai-org/GLM-5.2` | | inceptron | `inceptron/zai-org/GLM-5.3` | | meta | `meta/muse-spark-1.1` | | meta | `meta/muse-spark-1.2` | | meta | `meta/muse-spark-1.2-contributor` | | meta | `meta/muse-spark-1.3` | | meta | `meta/muse-spark-1.3-contributor` | | Minimax | `minimax/M2-her` | | Minimax | `minimax/MiniMax-M2` | | Minimax | `minimax/MiniMax-M2.1` | | Minimax | `minimax/MiniMax-M2.1-highspeed` | | Minimax | `minimax/MiniMax-M2.5` | | Minimax | `minimax/MiniMax-M2.5-highspeed` | | Minimax | `minimax/minimax-m2.5-lightning` | | Minimax | `minimax/MiniMax-M2.7` | | Minimax | `minimax/MiniMax-M2.7-highspeed` | | Minimax | `minimax/MiniMax-M3` | | Mistral | `mistral/codestral-2508` | | Mistral | `mistral/codestral-latest` | | Mistral | `mistral/labs-leanstral-1-5` | | Mistral | `mistral/labs-leanstral-1-5-1` | | Mistral | `mistral/ministral-14b-2512` | | Mistral | `mistral/ministral-14b-latest` | | Mistral | `mistral/ministral-3b-2512` | | Mistral | `mistral/ministral-3b-latest` | | Mistral | `mistral/ministral-8b-2512` | | Mistral | `mistral/ministral-8b-latest` | | Mistral | `mistral/mistral-large-2512` | | Mistral | `mistral/mistral-large-latest` | | Mistral | `mistral/mistral-medium-2604` | | Mistral | `mistral/mistral-medium-3` | | Mistral | `mistral/mistral-medium-3-5` | | Mistral | `mistral/mistral-medium-3.5` | | Mistral | `mistral/mistral-medium-latest` | | Mistral | `mistral/mistral-small-2603` | | Mistral | `mistral/mistral-small-latest` | | Mistral | `mistral/mistral-tiny-2407` | | Mistral | `mistral/mistral-tiny-latest` | | Mistral | `mistral/voxtral-small-2507` | | Mistral | `mistral/voxtral-small-latest` | | Mistral | `mistral/zai-glm-5-2` | | Moonshot AI | `moonshotai/kimi-k2.6` | | Moonshot AI | `moonshotai/kimi-k2.7-code` | | Moonshot AI | `moonshotai/kimi-k2.7-code-highspeed` | | Moonshot AI | `moonshotai/kimi-k3` | | nebius | `nebius/deepseek-ai/DeepSeek-V4-Pro` | | nebius | `nebius/google/gemma-3-27b-it` | | nebius | `nebius/MiniMaxAI/MiniMax-M3` | | nebius | `nebius/moonshotai/Kimi-K2.6` | | nebius | `nebius/moonshotai/Kimi-K2.7-Code` | | nebius | `nebius/moonshotai/Kimi-K3` | | nebius | `nebius/NousResearch/Hermes-4-405B` | | nebius | `nebius/nvidia/Nemotron-3_5-Lightning` | | nebius | `nebius/nvidia/nemotron-3-super-120b-a12b` | | nebius | `nebius/openai/gpt-oss-120b` | | nebius | `nebius/openbmb/MiniCPM-V-4_5` | | nebius | `nebius/Qwen/Qwen3-235B-A22B-Instruct-2507` | | nebius | `nebius/Qwen/Qwen3-30B-A3B-Instruct-2507` | | nebius | `nebius/Qwen/Qwen3.5-397B-A17B` | | nebius | `nebius/zai-org/GLM-5.1` | | nebius | `nebius/zai-org/GLM-5.2` | | Nvidia | `nvidia/meta/llama-3.1-8b-instruct` | | Nvidia | `nvidia/meta/muse-glimmer-30b` | | Nvidia | `nvidia/minimaxai/minimax-m3` | | Nvidia | `nvidia/moonshotai/kimi-k3` | | Nvidia | `nvidia/nvidia/nemotron-3-nano-30b-a3b` | | Nvidia | `nvidia/nvidia/nemotron-3-super-120b-a12b` | | Nvidia | `nvidia/nvidia/nemotron-3-ultra-550b-a55b` | | Nvidia | `nvidia/nvidia/nemotron-3.5-content-safety` | | Nvidia | `nvidia/nvidia/nemotron-3.5-lightning-30b-a3b` | | Nvidia | `nvidia/poolside/laguna-xs-2.1` | | Nvidia | `nvidia/thinkingmachines/inkling` | | Nvidia | `nvidia/z-ai/glm-5.2` | | OpenAI | `openai/eu.gpt-4.1` | | OpenAI | `openai/eu.gpt-4.1-2025-04-14` | | OpenAI | `openai/eu.gpt-4.1-mini` | | OpenAI | `openai/eu.gpt-4.1-mini-2025-04-14` | | OpenAI | `openai/eu.gpt-4o` | | OpenAI | `openai/eu.gpt-4o-2024-08-06` | | OpenAI | `openai/eu.gpt-4o-2024-11-20` | | OpenAI | `openai/eu.gpt-4o-mini` | | OpenAI | `openai/eu.gpt-4o-mini-2024-07-18` | | OpenAI | `openai/eu.gpt-5` | | OpenAI | `openai/eu.gpt-5-mini` | | OpenAI | `openai/eu.gpt-5-nano` | | OpenAI | `openai/eu.gpt-5.1` | | OpenAI | `openai/eu.gpt-5.1-2025-11-13` | | OpenAI | `openai/eu.gpt-5.2` | | OpenAI | `openai/eu.gpt-5.2-2025-12-11` | | OpenAI | `openai/eu.gpt-5.4` | | OpenAI | `openai/eu.gpt-5.4-2026-03-05` | | OpenAI | `openai/eu.gpt-5.4-mini` | | OpenAI | `openai/eu.gpt-5.4-mini-2026-03-17` | | OpenAI | `openai/eu.gpt-5.4-nano` | | OpenAI | `openai/eu.gpt-5.4-nano-2026-03-17` | | OpenAI | `openai/eu.gpt-5.5` | | OpenAI | `openai/eu.gpt-5.5-2026-04-23` | | OpenAI | `openai/eu.gpt-5.6-luna` | | OpenAI | `openai/eu.gpt-5.6-sol` | | OpenAI | `openai/eu.gpt-5.6-terra` | | OpenAI | `openai/eu.gpt-6-astra` | | OpenAI | `openai/eu.o1-2024-12-17` | | OpenAI | `openai/eu.o3` | | OpenAI | `openai/eu.o3-mini-2025-01-31` | | OpenAI | `openai/eu.o4-mini-2025-04-16` | | OpenAI | `openai/gpt-3.5-turbo-16k` | | OpenAI | `openai/gpt-4.1` | | OpenAI | `openai/gpt-4.1-2025-04-14` | | OpenAI | `openai/gpt-4.1-mini` | | OpenAI | `openai/gpt-4.1-mini-2025-04-14` | | OpenAI | `openai/gpt-4o` | | OpenAI | `openai/gpt-4o-2024-08-06` | | OpenAI | `openai/gpt-4o-2024-11-20` | | OpenAI | `openai/gpt-4o-mini` | | OpenAI | `openai/gpt-4o-mini-2024-07-18` | | OpenAI | `openai/gpt-5` | | OpenAI | `openai/gpt-5-mini` | | OpenAI | `openai/gpt-5-nano` | | OpenAI | `openai/gpt-5.1` | | OpenAI | `openai/gpt-5.1-2025-11-13` | | OpenAI | `openai/gpt-5.2` | | OpenAI | `openai/gpt-5.2-2025-12-11` | | OpenAI | `openai/gpt-5.3-codex` | | OpenAI | `openai/gpt-5.4` | | OpenAI | `openai/gpt-5.4-2026-03-05` | | OpenAI | `openai/gpt-5.4-mini` | | OpenAI | `openai/gpt-5.4-mini-2026-03-17` | | OpenAI | `openai/gpt-5.4-nano` | | OpenAI | `openai/gpt-5.4-nano-2026-03-17` | | OpenAI | `openai/gpt-5.5` | | OpenAI | `openai/gpt-5.5-2026-04-23` | | OpenAI | `openai/gpt-5.6-luna` | | OpenAI | `openai/gpt-5.6-sol` | | OpenAI | `openai/gpt-5.6-terra` | | OpenAI | `openai/gpt-6-astra` | | OpenAI | `openai/o1-2024-12-17` | | OpenAI | `openai/o3` | | OpenAI | `openai/o3-mini-2025-01-31` | | OpenAI | `openai/o4-mini-2025-04-16` | | Perplexity | `perplexity/sonar` | | Perplexity | `perplexity/sonar-deep-research` | | Perplexity | `perplexity/sonar-pro` | | Perplexity | `perplexity/sonar-reasoning-pro` | | poolside | `poolside/poolside/laguna-m.1` | | poolside | `poolside/poolside/laguna-s-2.1` | | poolside | `poolside/poolside/laguna-xs-2.1` | | scaleway | `scaleway/deepseek-v4-flash-0731` | | scaleway | `scaleway/gemma-4-26b-a4b-it` | | scaleway | `scaleway/glm-5.2` | | scaleway | `scaleway/gpt-oss-120b` | | scaleway | `scaleway/llama-3.3-70b-instruct` | | scaleway | `scaleway/mistral-medium-3.5-128b` | | scaleway | `scaleway/mistral-small-3.2-24b-instruct-2506` | | scaleway | `scaleway/qwen3-235b-a22b-instruct-2507` | | scaleway | `scaleway/qwen3.5-397b-a17b` | | scaleway | `scaleway/qwen3.6-35b-a3b` | | tencent | `tencent/deepseek-v4-flash` | | tencent | `tencent/deepseek-v4-flash-202605` | | tencent | `tencent/deepseek-v4-pro` | | tencent | `tencent/deepseek-v4-pro-202606` | | tencent | `tencent/glm-5` | | tencent | `tencent/glm-5-turbo` | | tencent | `tencent/glm-5.1` | | tencent | `tencent/glm-5.2` | | tencent | `tencent/glm-5v-turbo` | | tencent | `tencent/hy-mt2-plus` | | tencent | `tencent/kimi-k2.5` | | tencent | `tencent/kimi-k2.6` | | tencent | `tencent/kimi-k2.7-code` | | tencent | `tencent/kimi-k2.7-code-highspeed` | | tencent | `tencent/minimax-m2.5` | | tencent | `tencent/minimax-m2.7` | | tencent | `tencent/minimax-m3` | | tensorix | `tensorix/deepseek/deepseek-chat-v3.1` | | tensorix | `tensorix/deepseek/deepseek-r1-0528` | | tensorix | `tensorix/deepseek/deepseek-v3.2` | | tensorix | `tensorix/deepseek/deepseek-v4-flash-0731` | | tensorix | `tensorix/deepseek/deepseek-v4-pro` | | tensorix | `tensorix/meta-llama/llama-3.3-70b-instruct` | | tensorix | `tensorix/meta-llama/llama-4-maverick` | | tensorix | `tensorix/minimax/minimax-m2` | | tensorix | `tensorix/minimax/minimax-m2.5` | | tensorix | `tensorix/minimax/minimax-m3` | | tensorix | `tensorix/moonshotai/kimi-k2.5` | | tensorix | `tensorix/moonshotai/kimi-k2.6` | | tensorix | `tensorix/moonshotai/kimi-k2.7-code` | | tensorix | `tensorix/moonshotai/kimi-k3` | | tensorix | `tensorix/nvidia/nemotron-3-super-120b-a12b` | | tensorix | `tensorix/openai/gpt-oss-120b` | | tensorix | `tensorix/openai/gpt-oss-20b` | | tensorix | `tensorix/qwen/qwen3-235b-a22b-2507` | | tensorix | `tensorix/qwen/qwen3-coder-30b-a3b-instruct` | | tensorix | `tensorix/qwen/qwen3-vl-235b-a22b-instruct` | | tensorix | `tensorix/qwen/qwen3.5-122b-a10b` | | tensorix | `tensorix/qwen/qwen3.5-9b` | | tensorix | `tensorix/qwen/qwen3.8-2.4t-a95b` | | tensorix | `tensorix/qwen/qwen3.8-flash-next` | | tensorix | `tensorix/xiaomi/mimo-v2.5` | | tensorix | `tensorix/z-ai/glm-4.6` | | tensorix | `tensorix/z-ai/glm-4.7` | | tensorix | `tensorix/z-ai/glm-5` | | tensorix | `tensorix/z-ai/glm-5-turbo` | | tensorix | `tensorix/z-ai/glm-5.1` | | tensorix | `tensorix/z-ai/glm-5.2` | | tensorix | `tensorix/z-ai/glm-5.3-flash` | | tensorix | `tensorix/z-ai/glm-5v-turbo` | | Together AI | `togetherai/meta-llama/Llama-3.3-70B-Instruct-Turbo` | | Together AI | `togetherai/meta-llama/Llama-Guard-4-12B` | | Together AI | `togetherai/moonshotai/Kimi-K2.6` | | Together AI | `togetherai/moonshotai/Kimi-K2.7-Code` | | Together AI | `togetherai/zai-org/GLM-5.2` | | Wafer | `wafer/DeepSeek-V4-Flash-0731-Fast` | | Wafer | `wafer/GLM-5.1` | | Wafer | `wafer/GLM-5.2` | | Wafer | `wafer/glm5.2-fast` | | Wafer | `wafer/Kimi-K2.6` | | Wafer | `wafer/Kimi-K3` | | Wafer | `wafer/kimi-k3-fast` | | Wafer | `wafer/MiniMax-M3` | | Wafer | `wafer/Qwen3.5-397B-A17B` | | xai | `xai/grok-3-fast` | | xai | `xai/grok-3-fast-latest` | | xai | `xai/grok-3-latest` | | xai | `xai/grok-3-mini` | | xai | `xai/grok-3-mini-fast` | | xai | `xai/grok-3-mini-fast-latest` | | xai | `xai/grok-3-mini-latest` | | xai | `xai/grok-4` | | xai | `xai/grok-4-1-fast` | | xai | `xai/grok-4-fast` | | xai | `xai/grok-4.20-0309-non-reasoning` | | xai | `xai/grok-4.20-0309-reasoning` | | xai | `xai/grok-4.20-beta-latest-non-reasoning` | | xai | `xai/grok-4.20-beta-latest-reasoning` | | xai | `xai/grok-4.20-multi-agent-0309` | | xai | `xai/grok-4.20-multi-agent-beta-latest` | | xai | `xai/grok-4.3` | | xai | `xai/grok-4.5` | | xai | `xai/grok-4.6` | | xai | `xai/grok-build-0.1` | | Z AI | `zai/glm-4.5` | | Z AI | `zai/glm-4.5-air` | | Z AI | `zai/glm-4.5-flash` | | Z AI | `zai/glm-4.5v` | | Z AI | `zai/glm-4.6` | | Z AI | `zai/glm-4.6v` | | Z AI | `zai/glm-4.7` | | Z AI | `zai/glm-4.7-flash` | | Z AI | `zai/glm-4.7-flashx` | | Z AI | `zai/glm-5` | | Z AI | `zai/glm-5-turbo` | | Z AI | `zai/glm-5.1` | | Z AI | `zai/glm-5.2` | | Z AI | `zai/glm-5.3` | | Z AI | `zai/glm-5.3-flash` | | Z AI | `zai/glm-5v-turbo` | ### Chat models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "openai/gpt-4o", "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Hello!" } ] }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) completion = client.chat.completions.create( model="openai/gpt-4o", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello!"} ] ) print(completion.choices[0].message) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const completion = await client.chat.completions.create({ messages: [{ role: "system", content: "You are a helpful assistant." }], model: "openai/gpt-4o" }); console.log(completion.choices[0]); } main(); ``` | Provider | Model | | ----------- | ------------------------------------------------------ | | Alibaba | `alibaba/deepseek-v4-flash` | | Alibaba | `alibaba/deepseek-v4-pro` | | Alibaba | `alibaba/kimi-k2.5` | | Alibaba | `alibaba/kimi-k2.6` | | Alibaba | `alibaba/qwen-mt-flash` | | Alibaba | `alibaba/qwen-mt-lite` | | Alibaba | `alibaba/qwen-plus` | | Alibaba | `alibaba/qwen-plus-2025-12-01` | | Alibaba | `alibaba/qwen3-max` | | Alibaba | `alibaba/qwen3-vl-flash` | | Alibaba | `alibaba/qwen3-vl-flash-2025-10-15` | | Alibaba | `alibaba/qwen3-vl-plus` | | Alibaba | `alibaba/qwen3.5-122b-a10b` | | Alibaba | `alibaba/qwen3.5-27b` | | Alibaba | `alibaba/qwen3.5-35b-a3b` | | Alibaba | `alibaba/qwen3.5-flash` | | Alibaba | `alibaba/qwen3.5-flash-2026-02-23` | | Alibaba | `alibaba/qwen3.6-35b-a3b` | | Alibaba | `alibaba/qwen3.6-flash` | | Alibaba | `alibaba/qwen3.6-flash-2026-04-16` | | Alibaba | `alibaba/qwen3.6-plus` | | Alibaba | `alibaba/qwen3.6-plus-2026-04-02` | | Alibaba | `alibaba/qwen3.7-max` | | Alibaba | `alibaba/qwen3.7-max-2026-05-20` | | Alibaba | `alibaba/qwen3.7-max-2026-06-08` | | Alibaba | `alibaba/qwen3.7-plus` | | Alibaba | `alibaba/qwen3.7-plus-2026-05-26` | | Alibaba | `alibaba/qwen3.8-max` | | Anthropic | `anthropic/claude-fable-5` | | Anthropic | `anthropic/claude-fable-5-1` | | Anthropic | `anthropic/claude-haiku-4-5` | | Anthropic | `anthropic/claude-haiku-4-5-20251001` | | Anthropic | `anthropic/claude-opus-4-5` | | Anthropic | `anthropic/claude-opus-4-5-20251101` | | Anthropic | `anthropic/claude-opus-4-6` | | Anthropic | `anthropic/claude-opus-4-7` | | Anthropic | `anthropic/claude-opus-4-8` | | Anthropic | `anthropic/claude-opus-5` | | Anthropic | `anthropic/claude-sonnet-4-5` | | Anthropic | `anthropic/claude-sonnet-4-5-20250929` | | Anthropic | `anthropic/claude-sonnet-4-6` | | Anthropic | `anthropic/claude-sonnet-5` | | AWS Bedrock | `aws/ai21.jamba-1-5-mini-v1:0` | | AWS Bedrock | `aws/apac.amazon.nova-lite-v1:0` | | AWS Bedrock | `aws/apac.amazon.nova-micro-v1:0` | | AWS Bedrock | `aws/apac.amazon.nova-pro-v1:0` | | AWS Bedrock | `aws/apac.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/au.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/au.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/deepseek.v3.2` | | AWS Bedrock | `aws/deepseek.v3.2-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-lite-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-micro-v1:0` | | AWS Bedrock | `aws/eu.amazon.nova-pro-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-fable-5` | | AWS Bedrock | `aws/eu.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-5-20251101-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-6-v1` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-7` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-4-8` | | AWS Bedrock | `aws/eu.anthropic.claude-opus-5` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/eu.anthropic.claude-sonnet-5` | | AWS Bedrock | `aws/global.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-fable-5` | | AWS Bedrock | `aws/global.anthropic.claude-fable-5-1` | | AWS Bedrock | `aws/global.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-5-20251101-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-6-v1` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-7` | | AWS Bedrock | `aws/global.anthropic.claude-opus-4-8` | | AWS Bedrock | `aws/global.anthropic.claude-opus-5` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/global.anthropic.claude-sonnet-5` | | AWS Bedrock | `aws/google.gemma-3-27b-it` | | AWS Bedrock | `aws/jp.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/jp.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/jp.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/jp.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/meta.llama3-1-405b-instruct-v1:0` | | AWS Bedrock | `aws/minimax.minimax-m2.1` | | AWS Bedrock | `aws/minimax.minimax-m2.5` | | AWS Bedrock | `aws/mistral.devstral-2-123b` | | AWS Bedrock | `aws/mistral.magistral-small-2509` | | AWS Bedrock | `aws/mistral.ministral-3-3b-instruct` | | AWS Bedrock | `aws/mistral.mistral-large-3-675b-instruct` | | AWS Bedrock | `aws/mistral.pixtral-large-2502-v1:0` | | AWS Bedrock | `aws/moonshotai.kimi-k2.5` | | AWS Bedrock | `aws/nvidia.nemotron-nano-3-30b` | | AWS Bedrock | `aws/nvidia.nemotron-super-3-120b` | | AWS Bedrock | `aws/openai.gpt-oss-120b-1:0` | | AWS Bedrock | `aws/qwen.qwen3-32b-v1:0` | | AWS Bedrock | `aws/qwen.qwen3-coder-next` | | AWS Bedrock | `aws/us.amazon.nova-2-lite-v1:0` | | AWS Bedrock | `aws/us.amazon.nova-lite-v1:0` | | AWS Bedrock | `aws/us.amazon.nova-micro-v1:0` | | AWS Bedrock | `aws/us.amazon.nova-pro-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-fable-5` | | AWS Bedrock | `aws/us.anthropic.claude-fable-5-1` | | AWS Bedrock | `aws/us.anthropic.claude-haiku-4-5-20251001-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-opus-4-1-20250805-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-opus-4-5-20251101-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-opus-4-8` | | AWS Bedrock | `aws/us.anthropic.claude-opus-5` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-4-20250514-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-4-5-20250929-v1:0` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-4-6` | | AWS Bedrock | `aws/us.anthropic.claude-sonnet-5` | | AWS Bedrock | `aws/us.writer.palmyra-x5-v1:0` | | AWS Bedrock | `aws/writer.palmyra-x5-v1:0` | | AWS Bedrock | `aws/zai.glm-4.7` | | AWS Bedrock | `aws/zai.glm-5` | | Azure | `azure/eu.gpt-5.6-luna` | | Azure | `azure/eu.gpt-5.6-terra` | | Azure | `azure/global.gpt-5.6-luna` | | Azure | `azure/global.gpt-5.6-terra` | | Azure | `azure/gpt-4.1` | | Azure | `azure/gpt-4.1-mini` | | Azure | `azure/gpt-4.1-nano` | | Azure | `azure/gpt-4o` | | Azure | `azure/gpt-5` | | Azure | `azure/gpt-5-mini` | | Azure | `azure/gpt-5-nano` | | Azure | `azure/gpt-5.4` | | Azure | `azure/gpt-5.6-sol` | | Azure | `azure/gpt-5.6-sol-global` | | baseten | `baseten/deepseek-v4-flash-0731` | | baseten | `baseten/deepseek-v4-pro` | | baseten | `baseten/deepseek-v4-pro-0813` | | baseten | `baseten/glm-4.7` | | baseten | `baseten/glm-5.2` | | baseten | `baseten/glm-5.2-fast` | | baseten | `baseten/glm-5.3` | | baseten | `baseten/glm-5.3-flash` | | baseten | `baseten/gpt-oss-120b` | | baseten | `baseten/inkling` | | baseten | `baseten/inkling-small` | | baseten | `baseten/kimi-k2.6` | | baseten | `baseten/kimi-k2.7-code` | | baseten | `baseten/kimi-k3` | | baseten | `baseten/nemotron-ultra` | | Cerebras | `cerebras/gemma-4-31b` | | Cerebras | `cerebras/gpt-oss-120b` | | Cohere | `cohere/c4ai-aya-expanse-32b` | | Cohere | `cohere/c4ai-aya-vision-32b` | | Cohere | `cohere/command-a-03-2025` | | Cohere | `cohere/command-a-plus-05-2026` | | Cohere | `cohere/command-a-reasoning-08-2025` | | Cohere | `cohere/command-a-translate-08-2025` | | Cohere | `cohere/command-a-vision-07-2025` | | Cohere | `cohere/command-r-08-2024` | | Cohere | `cohere/command-r-plus-08-2024` | | Cohere | `cohere/command-r7b-12-2024` | | Cohere | `cohere/command-r7b-arabic-02-2025` | | Deepseek | `deepseek/deepseek-v4-flash` | | Deepseek | `deepseek/deepseek-v4-pro` | | fireworks | `fireworks/deepseek-v4-pro` | | fireworks | `fireworks/glm-5.2` | | fireworks | `fireworks/kimi-k2.7-code` | | fireworks | `fireworks/kimi-k3` | | fireworks | `fireworks/kimi-k3-fast` | | fireworks | `fireworks/minimax-m3` | | fireworks | `fireworks/qwen3.7-plus` | | Vertex AI | `google/claude-haiku-4-5` | | Vertex AI | `google/claude-opus-4-5@20251101` | | Vertex AI | `google/claude-opus-4-6` | | Vertex AI | `google/claude-opus-4-7` | | Vertex AI | `google/claude-opus-4-8` | | Vertex AI | `google/claude-sonnet-4-5@20250929` | | Vertex AI | `google/claude-sonnet-4-6` | | Vertex AI | `google/claude-sonnet-4@20250514` | | Vertex AI | `google/eu.claude-fable-5` | | Vertex AI | `google/eu.claude-fable-5-1` | | Vertex AI | `google/eu.claude-opus-4-7` | | Vertex AI | `google/eu.claude-opus-4-8` | | Vertex AI | `google/eu.claude-opus-5` | | Vertex AI | `google/eu.claude-sonnet-5` | | Vertex AI | `google/eu.gemini-3.1-flash-lite` | | Vertex AI | `google/eu.gemini-3.5-flash` | | Vertex AI | `google/eu.gemini-3.5-flash-lite` | | Vertex AI | `google/eu.gemini-3.6-flash` | | Vertex AI | `google/eu.gemini-3.7-flash` | | Vertex AI | `google/eu.gemini-3.8-flash` | | Vertex AI | `google/gemini-2.5-flash` | | Vertex AI | `google/gemini-2.5-flash-lite` | | Vertex AI | `google/gemini-2.5-pro` | | Vertex AI | `google/gemini-3-flash-preview` | | Vertex AI | `google/gemini-3.1-flash-lite` | | Vertex AI | `google/gemini-3.1-pro-preview` | | Vertex AI | `google/gemini-3.5-flash` | | Vertex AI | `google/gemini-3.5-flash-lite` | | Vertex AI | `google/gemini-3.6-flash` | | Vertex AI | `google/gemini-3.7-flash` | | Vertex AI | `google/gemini-3.8-flash` | | Vertex AI | `google/global.claude-fable-5` | | Vertex AI | `google/global.claude-fable-5-1` | | Vertex AI | `google/global.claude-opus-4-7` | | Vertex AI | `google/global.claude-opus-4-8` | | Vertex AI | `google/global.claude-opus-5` | | Vertex AI | `google/global.claude-sonnet-5` | | Vertex AI | `google/us.claude-fable-5` | | Vertex AI | `google/us.claude-fable-5-1` | | Vertex AI | `google/us.claude-opus-4-7` | | Vertex AI | `google/us.claude-opus-4-8` | | Vertex AI | `google/us.claude-opus-5` | | Vertex AI | `google/us.claude-sonnet-5` | | Vertex AI | `google/us.gemini-3.1-flash-lite` | | Vertex AI | `google/us.gemini-3.5-flash` | | Vertex AI | `google/us.gemini-3.5-flash-lite` | | Vertex AI | `google/us.gemini-3.6-flash` | | Vertex AI | `google/us.gemini-3.7-flash` | | Vertex AI | `google/us.gemini-3.8-flash` | | Google AI | `google-ai/gemini-2.5-flash` | | Google AI | `google-ai/gemini-2.5-flash-lite` | | Google AI | `google-ai/gemini-2.5-pro` | | Google AI | `google-ai/gemini-3-flash-preview` | | Google AI | `google-ai/gemini-3.1-pro-preview` | | Google AI | `google-ai/gemini-3.1-pro-preview-customtools` | | Google AI | `google-ai/gemini-3.5-flash` | | Google AI | `google-ai/gemini-3.5-flash-lite` | | Google AI | `google-ai/gemini-3.6-flash` | | Google AI | `google-ai/gemini-3.7-flash` | | Google AI | `google-ai/gemini-3.8-flash` | | Google AI | `google-ai/gemini-omni-flash-preview` | | Google AI | `google-ai/gemma-4-26b-a4b-it` | | Google AI | `google-ai/gemma-4-31b-it` | | greenpt | `greenpt/deepseek-v4-flash-0731` | | greenpt | `greenpt/gemma4` | | greenpt | `greenpt/glm-5.2` | | greenpt | `greenpt/glm-5.2-caveman` | | greenpt | `greenpt/glm-5.2-caveman-lite` | | greenpt | `greenpt/glm-5.2-caveman-ultra` | | greenpt | `greenpt/glm-5.2-honey` | | greenpt | `greenpt/glm-5.2-honey-lite` | | greenpt | `greenpt/glm-5.2-honey-ultra` | | greenpt | `greenpt/glm-5.2-ponytail` | | greenpt | `greenpt/glm-5.2-ponytail-lite` | | greenpt | `greenpt/glm-5.2-ponytail-ultra` | | greenpt | `greenpt/gpt-oss-120b` | | greenpt | `greenpt/green-l` | | greenpt | `greenpt/green-l-raw` | | greenpt | `greenpt/green-r` | | greenpt | `greenpt/green-r-raw` | | greenpt | `greenpt/kimi-k2.6` | | greenpt | `greenpt/kimi-k2.7-code` | | greenpt | `greenpt/kimi-k3` | | greenpt | `greenpt/llama-3.3-70b-instruct` | | greenpt | `greenpt/minimax-m2.5` | | greenpt | `greenpt/mistral-medium-3.5-128b` | | greenpt | `greenpt/mistral-small-3.2-24b-instruct-2506` | | greenpt | `greenpt/qwen3-235b-a22b-instruct-2507` | | greenpt | `greenpt/qwen3.5-397b-a17b` | | greenpt | `greenpt/qwen3.6-35b-a3b` | | Groq | `groq/allam-2-7b` | | Groq | `groq/groq/compound` | | Groq | `groq/groq/compound-mini` | | Groq | `groq/meta-llama/llama-prompt-guard-2-22m` | | Groq | `groq/meta-llama/llama-prompt-guard-2-86m` | | Groq | `groq/openai/gpt-oss-120b` | | Groq | `groq/openai/gpt-oss-20b` | | Groq | `groq/openai/gpt-oss-safeguard-20b` | | Groq | `groq/qwen/qwen3.6-27b` | | Groq | `groq/qwen/qwen3.8-27b` | | hcompany | `hcompany/holo3-1-35b-a3b` | | hcompany | `hcompany/holo3-122b-a10b` | | inceptron | `inceptron/deepseek-ai/DeepSeek-V4-Flash-0731` | | inceptron | `inceptron/MiniMaxAI/MiniMax-M2.5` | | inceptron | `inceptron/moonshotai/Kimi-K2.6` | | inceptron | `inceptron/moonshotai/Kimi-K2.7-Code` | | inceptron | `inceptron/zai-org/GLM-5.2` | | inceptron | `inceptron/zai-org/GLM-5.3` | | meta | `meta/muse-spark-1.1` | | meta | `meta/muse-spark-1.2` | | meta | `meta/muse-spark-1.2-contributor` | | meta | `meta/muse-spark-1.3` | | meta | `meta/muse-spark-1.3-contributor` | | Minimax | `minimax/M2-her` | | Minimax | `minimax/MiniMax-M2` | | Minimax | `minimax/MiniMax-M2.1` | | Minimax | `minimax/MiniMax-M2.1-highspeed` | | Minimax | `minimax/MiniMax-M2.5` | | Minimax | `minimax/MiniMax-M2.5-highspeed` | | Minimax | `minimax/minimax-m2.5-lightning` | | Minimax | `minimax/MiniMax-M2.7` | | Minimax | `minimax/MiniMax-M2.7-highspeed` | | Minimax | `minimax/MiniMax-M3` | | Mistral | `mistral/codestral-2508` | | Mistral | `mistral/codestral-latest` | | Mistral | `mistral/labs-leanstral-1-5` | | Mistral | `mistral/labs-leanstral-1-5-1` | | Mistral | `mistral/ministral-14b-2512` | | Mistral | `mistral/ministral-14b-latest` | | Mistral | `mistral/ministral-3b-2512` | | Mistral | `mistral/ministral-3b-latest` | | Mistral | `mistral/ministral-8b-2512` | | Mistral | `mistral/ministral-8b-latest` | | Mistral | `mistral/mistral-large-2512` | | Mistral | `mistral/mistral-large-latest` | | Mistral | `mistral/mistral-medium-2604` | | Mistral | `mistral/mistral-medium-3` | | Mistral | `mistral/mistral-medium-3-5` | | Mistral | `mistral/mistral-medium-3.5` | | Mistral | `mistral/mistral-medium-latest` | | Mistral | `mistral/mistral-small-2603` | | Mistral | `mistral/mistral-small-latest` | | Mistral | `mistral/mistral-tiny-2407` | | Mistral | `mistral/mistral-tiny-latest` | | Mistral | `mistral/voxtral-small-2507` | | Mistral | `mistral/voxtral-small-latest` | | Mistral | `mistral/zai-glm-5-2` | | Moonshot AI | `moonshotai/kimi-k2.6` | | Moonshot AI | `moonshotai/kimi-k2.7-code` | | Moonshot AI | `moonshotai/kimi-k2.7-code-highspeed` | | Moonshot AI | `moonshotai/kimi-k3` | | nebius | `nebius/deepseek-ai/DeepSeek-V4-Pro` | | nebius | `nebius/google/gemma-3-27b-it` | | nebius | `nebius/MiniMaxAI/MiniMax-M3` | | nebius | `nebius/moonshotai/Kimi-K2.6` | | nebius | `nebius/moonshotai/Kimi-K2.7-Code` | | nebius | `nebius/moonshotai/Kimi-K3` | | nebius | `nebius/NousResearch/Hermes-4-405B` | | nebius | `nebius/nvidia/Nemotron-3_5-Lightning` | | nebius | `nebius/nvidia/nemotron-3-super-120b-a12b` | | nebius | `nebius/openai/gpt-oss-120b` | | nebius | `nebius/openbmb/MiniCPM-V-4_5` | | nebius | `nebius/Qwen/Qwen3-235B-A22B-Instruct-2507` | | nebius | `nebius/Qwen/Qwen3-30B-A3B-Instruct-2507` | | nebius | `nebius/Qwen/Qwen3.5-397B-A17B` | | nebius | `nebius/zai-org/GLM-5.1` | | nebius | `nebius/zai-org/GLM-5.2` | | Nvidia | `nvidia/meta/llama-3.1-8b-instruct` | | Nvidia | `nvidia/meta/muse-glimmer-30b` | | Nvidia | `nvidia/minimaxai/minimax-m3` | | Nvidia | `nvidia/moonshotai/kimi-k3` | | Nvidia | `nvidia/nvidia/nemotron-3-nano-30b-a3b` | | Nvidia | `nvidia/nvidia/nemotron-3-super-120b-a12b` | | Nvidia | `nvidia/nvidia/nemotron-3-ultra-550b-a55b` | | Nvidia | `nvidia/nvidia/nemotron-3.5-content-safety` | | Nvidia | `nvidia/nvidia/nemotron-3.5-lightning-30b-a3b` | | Nvidia | `nvidia/poolside/laguna-xs-2.1` | | Nvidia | `nvidia/thinkingmachines/inkling` | | Nvidia | `nvidia/z-ai/glm-5.2` | | OpenAI | `openai/eu.gpt-4.1` | | OpenAI | `openai/eu.gpt-4.1-2025-04-14` | | OpenAI | `openai/eu.gpt-4.1-mini` | | OpenAI | `openai/eu.gpt-4.1-mini-2025-04-14` | | OpenAI | `openai/eu.gpt-4o` | | OpenAI | `openai/eu.gpt-4o-2024-08-06` | | OpenAI | `openai/eu.gpt-4o-2024-11-20` | | OpenAI | `openai/eu.gpt-4o-mini` | | OpenAI | `openai/eu.gpt-4o-mini-2024-07-18` | | OpenAI | `openai/eu.gpt-5` | | OpenAI | `openai/eu.gpt-5-mini` | | OpenAI | `openai/eu.gpt-5-nano` | | OpenAI | `openai/eu.gpt-5.1` | | OpenAI | `openai/eu.gpt-5.1-2025-11-13` | | OpenAI | `openai/eu.gpt-5.2` | | OpenAI | `openai/eu.gpt-5.2-2025-12-11` | | OpenAI | `openai/eu.gpt-5.4` | | OpenAI | `openai/eu.gpt-5.4-2026-03-05` | | OpenAI | `openai/eu.gpt-5.4-mini` | | OpenAI | `openai/eu.gpt-5.4-mini-2026-03-17` | | OpenAI | `openai/eu.gpt-5.4-nano` | | OpenAI | `openai/eu.gpt-5.4-nano-2026-03-17` | | OpenAI | `openai/eu.gpt-5.5` | | OpenAI | `openai/eu.gpt-5.5-2026-04-23` | | OpenAI | `openai/eu.gpt-5.6-luna` | | OpenAI | `openai/eu.gpt-5.6-sol` | | OpenAI | `openai/eu.gpt-5.6-terra` | | OpenAI | `openai/eu.gpt-6-astra` | | OpenAI | `openai/eu.o1-2024-12-17` | | OpenAI | `openai/eu.o3` | | OpenAI | `openai/eu.o3-mini-2025-01-31` | | OpenAI | `openai/eu.o4-mini-2025-04-16` | | OpenAI | `openai/gpt-3.5-turbo-16k` | | OpenAI | `openai/gpt-4.1` | | OpenAI | `openai/gpt-4.1-2025-04-14` | | OpenAI | `openai/gpt-4.1-mini` | | OpenAI | `openai/gpt-4.1-mini-2025-04-14` | | OpenAI | `openai/gpt-4o` | | OpenAI | `openai/gpt-4o-2024-08-06` | | OpenAI | `openai/gpt-4o-2024-11-20` | | OpenAI | `openai/gpt-4o-mini` | | OpenAI | `openai/gpt-4o-mini-2024-07-18` | | OpenAI | `openai/gpt-5` | | OpenAI | `openai/gpt-5-mini` | | OpenAI | `openai/gpt-5-nano` | | OpenAI | `openai/gpt-5.1` | | OpenAI | `openai/gpt-5.1-2025-11-13` | | OpenAI | `openai/gpt-5.2` | | OpenAI | `openai/gpt-5.2-2025-12-11` | | OpenAI | `openai/gpt-5.3-codex` | | OpenAI | `openai/gpt-5.4` | | OpenAI | `openai/gpt-5.4-2026-03-05` | | OpenAI | `openai/gpt-5.4-mini` | | OpenAI | `openai/gpt-5.4-mini-2026-03-17` | | OpenAI | `openai/gpt-5.4-nano` | | OpenAI | `openai/gpt-5.4-nano-2026-03-17` | | OpenAI | `openai/gpt-5.5` | | OpenAI | `openai/gpt-5.5-2026-04-23` | | OpenAI | `openai/gpt-5.6-luna` | | OpenAI | `openai/gpt-5.6-sol` | | OpenAI | `openai/gpt-5.6-terra` | | OpenAI | `openai/gpt-6-astra` | | OpenAI | `openai/o1-2024-12-17` | | OpenAI | `openai/o3` | | OpenAI | `openai/o3-mini-2025-01-31` | | OpenAI | `openai/o4-mini-2025-04-16` | | Perplexity | `perplexity/sonar` | | Perplexity | `perplexity/sonar-deep-research` | | Perplexity | `perplexity/sonar-pro` | | Perplexity | `perplexity/sonar-reasoning-pro` | | poolside | `poolside/poolside/laguna-m.1` | | poolside | `poolside/poolside/laguna-s-2.1` | | poolside | `poolside/poolside/laguna-xs-2.1` | | scaleway | `scaleway/deepseek-v4-flash-0731` | | scaleway | `scaleway/gemma-4-26b-a4b-it` | | scaleway | `scaleway/glm-5.2` | | scaleway | `scaleway/gpt-oss-120b` | | scaleway | `scaleway/llama-3.3-70b-instruct` | | scaleway | `scaleway/mistral-medium-3.5-128b` | | scaleway | `scaleway/mistral-small-3.2-24b-instruct-2506` | | scaleway | `scaleway/qwen3-235b-a22b-instruct-2507` | | scaleway | `scaleway/qwen3.5-397b-a17b` | | scaleway | `scaleway/qwen3.6-35b-a3b` | | tencent | `tencent/deepseek-v4-flash` | | tencent | `tencent/deepseek-v4-flash-202605` | | tencent | `tencent/deepseek-v4-pro` | | tencent | `tencent/deepseek-v4-pro-202606` | | tencent | `tencent/glm-5` | | tencent | `tencent/glm-5-turbo` | | tencent | `tencent/glm-5.1` | | tencent | `tencent/glm-5.2` | | tencent | `tencent/glm-5v-turbo` | | tencent | `tencent/hy-mt2-plus` | | tencent | `tencent/kimi-k2.5` | | tencent | `tencent/kimi-k2.6` | | tencent | `tencent/kimi-k2.7-code` | | tencent | `tencent/kimi-k2.7-code-highspeed` | | tencent | `tencent/minimax-m2.5` | | tencent | `tencent/minimax-m2.7` | | tencent | `tencent/minimax-m3` | | tensorix | `tensorix/deepseek/deepseek-chat-v3.1` | | tensorix | `tensorix/deepseek/deepseek-r1-0528` | | tensorix | `tensorix/deepseek/deepseek-v3.2` | | tensorix | `tensorix/deepseek/deepseek-v4-flash-0731` | | tensorix | `tensorix/deepseek/deepseek-v4-pro` | | tensorix | `tensorix/meta-llama/llama-3.3-70b-instruct` | | tensorix | `tensorix/meta-llama/llama-4-maverick` | | tensorix | `tensorix/minimax/minimax-m2` | | tensorix | `tensorix/minimax/minimax-m2.5` | | tensorix | `tensorix/minimax/minimax-m3` | | tensorix | `tensorix/moonshotai/kimi-k2.5` | | tensorix | `tensorix/moonshotai/kimi-k2.6` | | tensorix | `tensorix/moonshotai/kimi-k2.7-code` | | tensorix | `tensorix/moonshotai/kimi-k3` | | tensorix | `tensorix/nvidia/nemotron-3-super-120b-a12b` | | tensorix | `tensorix/openai/gpt-oss-120b` | | tensorix | `tensorix/openai/gpt-oss-20b` | | tensorix | `tensorix/qwen/qwen3-235b-a22b-2507` | | tensorix | `tensorix/qwen/qwen3-coder-30b-a3b-instruct` | | tensorix | `tensorix/qwen/qwen3-vl-235b-a22b-instruct` | | tensorix | `tensorix/qwen/qwen3.5-122b-a10b` | | tensorix | `tensorix/qwen/qwen3.5-9b` | | tensorix | `tensorix/qwen/qwen3.8-2.4t-a95b` | | tensorix | `tensorix/qwen/qwen3.8-flash-next` | | tensorix | `tensorix/xiaomi/mimo-v2.5` | | tensorix | `tensorix/z-ai/glm-4.6` | | tensorix | `tensorix/z-ai/glm-4.7` | | tensorix | `tensorix/z-ai/glm-5` | | tensorix | `tensorix/z-ai/glm-5-turbo` | | tensorix | `tensorix/z-ai/glm-5.1` | | tensorix | `tensorix/z-ai/glm-5.2` | | tensorix | `tensorix/z-ai/glm-5.3-flash` | | tensorix | `tensorix/z-ai/glm-5v-turbo` | | Together AI | `togetherai/meta-llama/Llama-3.3-70B-Instruct-Turbo` | | Together AI | `togetherai/meta-llama/Llama-Guard-4-12B` | | Together AI | `togetherai/moonshotai/Kimi-K2.6` | | Together AI | `togetherai/moonshotai/Kimi-K2.7-Code` | | Together AI | `togetherai/zai-org/GLM-5.2` | | Wafer | `wafer/DeepSeek-V4-Flash-0731-Fast` | | Wafer | `wafer/GLM-5.1` | | Wafer | `wafer/GLM-5.2` | | Wafer | `wafer/glm5.2-fast` | | Wafer | `wafer/Kimi-K2.6` | | Wafer | `wafer/Kimi-K3` | | Wafer | `wafer/kimi-k3-fast` | | Wafer | `wafer/MiniMax-M3` | | Wafer | `wafer/Qwen3.5-397B-A17B` | | xai | `xai/grok-3-fast` | | xai | `xai/grok-3-fast-latest` | | xai | `xai/grok-3-latest` | | xai | `xai/grok-3-mini` | | xai | `xai/grok-3-mini-fast` | | xai | `xai/grok-3-mini-fast-latest` | | xai | `xai/grok-3-mini-latest` | | xai | `xai/grok-4` | | xai | `xai/grok-4-1-fast` | | xai | `xai/grok-4-fast` | | xai | `xai/grok-4.20-0309-non-reasoning` | | xai | `xai/grok-4.20-0309-reasoning` | | xai | `xai/grok-4.20-beta-latest-non-reasoning` | | xai | `xai/grok-4.20-beta-latest-reasoning` | | xai | `xai/grok-4.20-multi-agent-0309` | | xai | `xai/grok-4.20-multi-agent-beta-latest` | | xai | `xai/grok-4.3` | | xai | `xai/grok-4.5` | | xai | `xai/grok-4.6` | | xai | `xai/grok-build-0.1` | | Z AI | `zai/glm-4.5` | | Z AI | `zai/glm-4.5-air` | | Z AI | `zai/glm-4.5-flash` | | Z AI | `zai/glm-4.5v` | | Z AI | `zai/glm-4.6` | | Z AI | `zai/glm-4.6v` | | Z AI | `zai/glm-4.7` | | Z AI | `zai/glm-4.7-flash` | | Z AI | `zai/glm-4.7-flashx` | | Z AI | `zai/glm-5` | | Z AI | `zai/glm-5-turbo` | | Z AI | `zai/glm-5.1` | | Z AI | `zai/glm-5.2` | | Z AI | `zai/glm-5.3` | | Z AI | `zai/glm-5.3-flash` | | Z AI | `zai/glm-5v-turbo` | ### Completion models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "openai/gpt-3.5-turbo-instruct", "prompt": "Once upon a time", "max_tokens": 100 }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) completion = client.completions.create( model="openai/gpt-3.5-turbo-instruct", prompt="Once upon a time", max_tokens=100 ) print(completion.choices[0].text) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const completion = await client.completions.create({ model: "openai/gpt-3.5-turbo-instruct", prompt: "Once upon a time", max_tokens: 100 }); console.log(completion.choices[0].text); } main(); ``` | Provider | Model | | -------- | ------------------------------- | | OpenAI | `openai/gpt-3.5-turbo-instruct` | ### Embedding models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/embeddings \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "openai/text-embedding-3-small", "input": "Hello world" }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) embedding = client.embeddings.create( model="openai/text-embedding-3-small", input="Hello world" ) print(embedding.data[0].embedding) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const embedding = await client.embeddings.create({ model: "openai/text-embedding-3-small", input: "Hello world" }); console.log(embedding.data[0].embedding); } main(); ``` | Provider | Model | | ----------- | ---------------------------------------- | | AWS Bedrock | `aws/amazon.titan-embed-g1-text-02` | | AWS Bedrock | `aws/amazon.titan-embed-image-v1` | | AWS Bedrock | `aws/amazon.titan-embed-text-v2:0` | | Azure | `azure/text-embedding-3-small` | | Azure | `azure/text-embedding-ada-002` | | Cohere | `cohere/embed-english-light-v3.0` | | Cohere | `cohere/embed-english-v3.0` | | Cohere | `cohere/embed-multilingual-light-v3.0` | | Cohere | `cohere/embed-multilingual-v3.0` | | Cohere | `cohere/embed-v4.0` | | Vertex AI | `google/eu.gemini-embedding-2` | | Vertex AI | `google/gemini-embedding-001` | | Vertex AI | `google/gemini-embedding-2` | | Vertex AI | `google/multimodalembedding@001` | | Vertex AI | `google/text-multilingual-embedding-002` | | Vertex AI | `google/us.gemini-embedding-2` | | Google AI | `google-ai/gemini-embedding-001` | | Google AI | `google-ai/gemini-embedding-2` | | greenpt | `greenpt/bge-multilingual-gemma2` | | greenpt | `greenpt/green-embedding` | | greenpt | `greenpt/green-embeddings` | | greenpt | `greenpt/qwen3-embedding-8b` | | Jina AI | `jina/jina-clip-v1` | | Jina AI | `jina/jina-clip-v2` | | Jina AI | `jina/jina-code-embeddings-0.5b` | | Jina AI | `jina/jina-code-embeddings-1.5b` | | Jina AI | `jina/jina-embeddings-v2-base-code` | | Jina AI | `jina/jina-embeddings-v2-base-de` | | Jina AI | `jina/jina-embeddings-v2-base-en` | | Jina AI | `jina/jina-embeddings-v2-base-es` | | Jina AI | `jina/jina-embeddings-v2-base-zh` | | Jina AI | `jina/jina-embeddings-v3` | | Jina AI | `jina/jina-embeddings-v4` | | Jina AI | `jina/jina-embeddings-v5-omni-nano` | | Jina AI | `jina/jina-embeddings-v5-omni-small` | | Jina AI | `jina/jina-embeddings-v5-text-nano` | | Jina AI | `jina/jina-embeddings-v5-text-small` | | Mistral | `mistral/codestral-embed` | | Mistral | `mistral/codestral-embed-2505` | | Mistral | `mistral/mistral-embed` | | Mistral | `mistral/mistral-embed-2312` | | nebius | `nebius/Qwen/Qwen3-Embedding-8B` | | Nvidia | `nvidia/nvidia/nemotron-3-embed-1b` | | OpenAI | `openai/eu.text-embedding-3-large` | | OpenAI | `openai/eu.text-embedding-3-small` | | OpenAI | `openai/eu.text-embedding-ada-002` | | OpenAI | `openai/text-embedding-3-large` | | OpenAI | `openai/text-embedding-3-small` | | OpenAI | `openai/text-embedding-ada-002` | | scaleway | `scaleway/bge-multilingual-gemma2` | | scaleway | `scaleway/qwen3-embedding-8b` | | tencent | `tencent/kinfra-text-embedding-0.6b` | | tencent | `tencent/kinfra-text-embedding-4b` | | tensorix | `tensorix/qwen/qwen3-embedding-8b` | ### Image models #### Image Generation ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/images/generations \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "openai/dall-e-3", "prompt": "A beautiful sunset over mountains", "n": 1, "size": "1024x1024" }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) response = client.images.generate( model="openai/dall-e-3", prompt="A beautiful sunset over mountains", n=1, size="1024x1024" ) print(response.data[0].url) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const response = await client.images.generate({ model: "openai/dall-e-3", prompt: "A beautiful sunset over mountains", n: 1, size: "1024x1024" }); console.log(response.data[0].url); } main(); ``` #### Image Edit ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/images/edits \ -H "Authorization: Bearer $ORQ_API_KEY" \ -F model="openai/gpt-image-2" \ -F image="@body-lotion.png" \ -F image="@bath-bomb.png" \ -F image="@incense-kit.png" \ -F image="@soap.png" \ -F prompt="Generate a photorealistic image of a gift basket on a white background labeled 'Relax & Unwind' with a ribbon and handwriting-like font, containing all the items in the reference pictures." ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import base64 from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) prompt = """ Generate a photorealistic image of a gift basket on a white background labeled 'Relax & Unwind' with a ribbon and handwriting-like font, containing all the items in the reference pictures. """ result = client.images.edit( model="openai/gpt-image-2", image=[ open("body-lotion.png", "rb"), open("bath-bomb.png", "rb"), open("incense-kit.png", "rb"), open("soap.png", "rb"), ], prompt=prompt ) image_base64 = result.data[0].b64_json image_bytes = base64.b64decode(image_base64) # Save the image to a file with open("gift-basket.png", "wb") as f: f.write(image_bytes) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import fs from "fs"; import OpenAI, { toFile } from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); const imageFiles = [ "bath-bomb.png", "body-lotion.png", "incense-kit.png", "soap.png", ]; const images = await Promise.all( imageFiles.map(async (file) => await toFile(fs.createReadStream(file), null, { type: "image/png", }) ), ); const response = await client.images.edit({ model: "openai/gpt-image-2", image: images, prompt: "Create a lovely gift basket with these four items in it", }); // Save the image to a file const image_base64 = response.data[0].b64_json; const image_bytes = Buffer.from(image_base64, "base64"); fs.writeFileSync("basket.png", image_bytes); ``` #### Image Variations ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/images/variations \ -H "Authorization: Bearer $ORQ_API_KEY" \ -F model="openai/dall-e-2" \ -F image="@image_edit_original.png" \ -F n=2 \ -F size="1024x1024" ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) response = client.images.create_variation( model="openai/dall-e-2", image=open("image_edit_original.png", "rb"), n=2, size="1024x1024" ) print(response.data[0].url) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import fs from "fs"; import OpenAI from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const response = await client.images.createVariation({ model: "openai/dall-e-2", image: fs.createReadStream("otter.png"), n: 2, size: "1024x1024" }); console.log(response.data); } main(); ``` #### Supported Image Models | Provider | Model | Capabilities | | ----------- | --------------------------------------------- | ---------------- | | bytedance | `bytedance/seedream-4-0-250828` | Generation, Edit | | bytedance | `bytedance/seedream-4-5-251128` | Generation, Edit | | FAL | `fal/flux-2` | Generation, Edit | | FAL | `fal/flux-2-flex` | Generation, Edit | | FAL | `fal/flux-2-max` | Generation, Edit | | FAL | `fal/flux-2-pro` | Generation, Edit | | FAL | `fal/flux-pro/v1.1` | Generation | | FAL | `fal/flux/dev` | Generation | | FAL | `fal/flux/schnell` | Generation | | FAL | `fal/gemini-25-flash-image` | Generation | | Vertex AI | `google/gemini-2.5-flash-image` | Generation | | Vertex AI | `google/gemini-3.1-flash-image` | Generation | | Vertex AI | `google/imagen-3.0-fast-generate-001` | Generation | | Vertex AI | `google/imagen-3.0-generate-001` | Generation | | Vertex AI | `google/imagen-4.0-fast-generate-001` | Generation | | Vertex AI | `google/imagen-4.0-generate-001` | Generation | | Vertex AI | `google/imagen-4.0-ultra-generate-001` | Generation | | Google AI | `google-ai/gemini-3-pro-image` | Generation | | Google AI | `google-ai/gemini-3.1-flash-image` | Generation | | Google AI | `google-ai/gemini-3.1-flash-lite-image` | Generation | | Leonardo AI | `leonardoai/leonard-diffusion-xl` | Generation | | Leonardo AI | `leonardoai/leonard-kino-xl` | Generation | | Leonardo AI | `leonardoai/leonard-lightning-xl` | Generation | | Leonardo AI | `leonardoai/leonard-vision-xl` | Generation | | meta | `meta/muse-image-1.0` | Generation, Edit | | OpenAI | `openai/eu.gpt-image-2` | Generation, Edit | | OpenAI | `openai/eu.gpt-image-2-2026-04-21` | Generation, Edit | | OpenAI | `openai/eu.gpt-image-2.5-flare` | Generation, Edit | | OpenAI | `openai/eu.gpt-image-2.5-flare-2026-09-08` | Generation, Edit | | OpenAI | `openai/eu.gpt-image-2.5-sunburst` | Generation, Edit | | OpenAI | `openai/eu.gpt-image-2.5-sunburst-2026-09-08` | Generation, Edit | | OpenAI | `openai/gpt-image-2` | Generation, Edit | | OpenAI | `openai/gpt-image-2-2026-04-21` | Generation, Edit | | OpenAI | `openai/gpt-image-2.5-flare` | Generation, Edit | | OpenAI | `openai/gpt-image-2.5-flare-2026-09-08` | Generation, Edit | | OpenAI | `openai/gpt-image-2.5-sunburst` | Generation, Edit | | OpenAI | `openai/gpt-image-2.5-sunburst-2026-09-08` | Generation, Edit | | Z AI | `zai/cogview-4-250304` | Generation | ### Moderations models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/moderations \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "openai/omni-moderation-latest", "input": "I want to check if this text is appropriate." }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) response = client.moderations.create( model="openai/omni-moderation-latest", input="I want to check if this text is appropriate." ) print(response.results[0]) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const response = await client.moderations.create({ model: "openai/omni-moderation-latest", input: "I want to check if this text is appropriate.", }); console.log(response.results[0]); } main(); ``` | Provider | Model | | -------- | -------------------------------------- | | Mistral | `mistral/mistral-moderation-2411` | | Mistral | `mistral/mistral-moderation-2603` | | OpenAI | `openai/eu.omni-moderation-2024-09-26` | | OpenAI | `openai/eu.omni-moderation-latest` | | OpenAI | `openai/omni-moderation-2024-09-26` | | OpenAI | `openai/omni-moderation-latest` | ### Rerank models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/rerank \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "cohere/rerank-english-v3.0", "query": "What is machine learning?", "documents": [ "Machine learning is a branch of AI", "Machine learning uses data to improve", "AI is changing the world" ] }' ``` | Provider | Model | | ----------- | ----------------------------------------- | | AWS Bedrock | `aws/amazon.rerank-v1:0` | | Cohere | `cohere/rerank-english-v3.0` | | Cohere | `cohere/rerank-multilingual-v3.0` | | Cohere | `cohere/rerank-v3.5` | | Cohere | `cohere/rerank-v4.0-fast` | | Cohere | `cohere/rerank-v4.0-pro` | | greenpt | `greenpt/green-rerank` | | Jina AI | `jina/jina-colbert-v2` | | Jina AI | `jina/jina-reranker-m0` | | Jina AI | `jina/jina-reranker-v1-base-en` | | Jina AI | `jina/jina-reranker-v1-tiny-en` | | Jina AI | `jina/jina-reranker-v1-turbo-en` | | Jina AI | `jina/jina-reranker-v2-base-multilingual` | | Jina AI | `jina/jina-reranker-v3` | | Jina AI | `jina/jina-reranker-v3.5` | ### OCR models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/ocr \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ORQ_API_KEY" \ -d '{ "model": "mistral/mistral-ocr-4-0", "document": { "type": "document_url", "document_url": "https://example.com/document.pdf" } }' ``` | Provider | Model | | -------- | ------------------------- | | Mistral | `mistral/mistral-ocr-4-0` | ### Speech-to-Text models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/audio/transcriptions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -F file="@/path/to/audio.mp3" \ -F model="openai/whisper-1" ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) audio_file = open("speech.mp3", "rb") transcript = client.audio.transcriptions.create( model="openai/whisper-1", file=audio_file ) print(transcript.text) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const transcript = await client.audio.transcriptions.create({ file: fs.createReadStream("speech.mp3"), model: "openai/whisper-1", }); console.log(transcript.text); } main(); ``` | Provider | Model | | ----------- | ------------------------------------------ | | Azure | `azure/whisper` | | Eleven Labs | `elevenlabs/scribe_v2` | | Google AI | `google-ai/gemini-3.5-transcribe` | | Google AI | `google-ai/gemini-3.5-transcribe-live` | | greenpt | `greenpt/green-s` | | greenpt | `greenpt/green-s-pro` | | Groq | `groq/whisper-large-v3` | | Groq | `groq/whisper-large-v3-turbo` | | meta | `meta/muse-voice-transcribe-1.0` | | Mistral | `mistral/voxtral-mini-2507` | | OpenAI | `openai/eu.gpt-4o-mini-transcribe` | | OpenAI | `openai/eu.gpt-4o-transcribe` | | OpenAI | `openai/eu.whisper-1` | | OpenAI | `openai/gpt-4o-mini-transcribe` | | OpenAI | `openai/gpt-4o-transcribe` | | OpenAI | `openai/gpt-4o-transcribe-diarize` | | OpenAI | `openai/gpt-live-transcribe` | | OpenAI | `openai/gpt-realtime-whisper` | | OpenAI | `openai/gpt-transcribe` | | OpenAI | `openai/whisper-1` | | reson8 | `reson8/prerecorded` | | scaleway | `scaleway/whisper-large-v3` | | tensorix | `tensorix/Systran/faster-whisper-large-v3` | | Together AI | `togetherai/nvidia/parakeet-tdt-0.6b-v3` | | Together AI | `togetherai/openai/whisper-large-v3` | ### Text-to-Speech models ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl https://my.orq.ai/v3/router/audio/speech \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/tts-1", "input": "Hello world!", "voice": "alloy" }' --output speech.mp3 ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os client = OpenAI( base_url="https://my.orq.ai/v3/router", api_key=os.getenv("ORQ_API_KEY"), ) response = client.audio.speech.create( model="openai/tts-1", voice="alloy", input="Hello world!" ) response.stream_to_file("speech.mp3") ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; import fs from "fs"; const client = new OpenAI({ baseURL: 'https://my.orq.ai/v3/router', apiKey: process.env.ORQ_API_KEY, }); async function main() { const mp3 = await client.audio.speech.create({ model: "openai/tts-1", voice: "alloy", input: "Hello world!", }); const buffer = Buffer.from(await mp3.arrayBuffer()); await fs.promises.writeFile("speech.mp3", buffer); } main(); ``` | Provider | Model | | ----------- | ----------------------------------------- | | Eleven Labs | `elevenlabs/eleven_flash_v2` | | Eleven Labs | `elevenlabs/eleven_flash_v2_5` | | Eleven Labs | `elevenlabs/eleven_multilingual_v2` | | Eleven Labs | `elevenlabs/eleven_turbo_v2_5` | | Eleven Labs | `elevenlabs/eleven_v3` | | Vertex AI | `google/gemini-2.5-flash-preview-tts` | | Vertex AI | `google/gemini-2.5-pro-preview-tts` | | Google AI | `google-ai/gemini-2.5-flash-preview-tts` | | Google AI | `google-ai/gemini-2.5-pro-preview-tts` | | Google AI | `google-ai/gemini-3.1-flash-tts-preview` | | Groq | `groq/canopylabs/orpheus-arabic-saudi` | | Groq | `groq/canopylabs/orpheus-v1-english` | | OpenAI | `openai/eu.gpt-4o-mini-tts` | | OpenAI | `openai/eu.tts-1` | | OpenAI | `openai/eu.tts-1-hd` | | OpenAI | `openai/gpt-4o-mini-tts` | | OpenAI | `openai/tts-1` | | OpenAI | `openai/tts-1-hd` | | tensorix | `tensorix/chatterbox-turbo` | | Together AI | `togetherai/canopylabs/orpheus-3b-0.1-ft` | | Together AI | `togetherai/cartesia/sonic-2` | | Together AI | `togetherai/cartesia/sonic-3` | | Together AI | `togetherai/hexgrad/Kokoro-82M` | #### Text-to-Speech Voices The following voices are available for Text-to-Speech models: #### OpenAI * `alloy`: Neutral, versatile voice * `echo`: Neutral, soft-spoken voice * `fable`: Expressive, narrative-focused voice * `onyx`: Deep, authoritative voice * `nova`: Warm, natural voice * `shimmer`: Clear, optimistic voice #### ElevenLabs * `aria`: Neutral, versatile voice * `roger`: Deep, authoritative voice * `sarah`: Warm, friendly voice * `laura`: Soft, gentle voice * `charlie`: Casual, conversational voice * `george`: Professional, articulate voice * `callum`: Youthful, energetic voice * `river`: Calm, soothing voice * `liam`: Clear, confident voice * `charlotte`: Elegant, refined voice * `alice`: Bright, cheerful voice * `matilda`: Thoughtful, measured voice * `will`: Reliable, trustworthy voice * `jessica`: Engaging, expressive voice * `eric`: Authoritative, commanding voice * `chris`: Friendly, approachable voice * `brian`: Mature, distinguished voice * `daniel`: Versatile, balanced voice * `lily`: Sweet, melodious voice * `bill`: Grounded, authentic voice *** [Retries & Error Handling](/docs/ai-gateway/features/retries) [Streaming](/docs/ai-gateway/features/streaming) # Thread management for grouped requests Source: https://docs.orq.ai/docs/ai-gateway/thread-management Group related AI Gateway requests into conversation threads for observability. Threads label related calls together without storing message history. **Use Cases** * Viewing all turns of a multi-turn conversation as a single trace in observability. * Debugging agent workflows by inspecting the full request sequence end-to-end. * Organizing support conversations to analyze resolution patterns across sessions. * Separating concurrent conversations from the same user to avoid mixing traces. For the full set of ways to attach context to a request, including app name, identity, and custom metadata, see [Request Metadata](/docs/ai-gateway/request-metadata). *** Group related requests into conversation Threads so they appear together in observability. Threads are a labeling mechanism and do not store or inject message history. Grouping is manual and opt-in: pass the same `thread.id` on every request that belongs to a conversation. API requests without a thread ID appear as individual traces and are never grouped automatically, with one exception: [Claude Code](/docs/ai-studio/integrations/code-assistants/claude-code) and [Codex](/docs/ai-studio/integrations/code-assistants/codex) send their own session identifiers, which are detected and grouped automatically. ## Quick Start ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Start a new conversation about AI", "orq": { "thread": { "id": "conversation-abc123", "tags": ["ai-discussion", "user-123"] } } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Start a new conversation about AI", orq: { thread: { id: `conversation-${crypto.randomUUID()}`, tags: ["ai-discussion", "user-123"], }, }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from openai import OpenAI import os import uuid client = OpenAI( api_key=os.environ.get("ORQ_API_KEY"), base_url="https://my.orq.ai/v3/router", ) response = client.responses.create( model="openai/gpt-5.6-sol", input="Start a new conversation about AI", extra_body={ "orq": { "thread": { "id": f"conversation-{uuid.uuid4()}", "tags": ["ai-discussion", "user-123"], } } }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Start a new conversation about AI" }], orq: { thread: { id: `conversation-${crypto.randomUUID()}`, tags: ["ai-discussion", "user-123"], }, }, }); ``` ## Configuration | Parameter | Type | Required | Description | | --------- | --------- | -------- | ---------------------------------------------- | | `id` | string | Yes | Unique thread identifier for grouping requests | | `tags` | string\[] | No | Metadata tags for filtering and categorization | ## Best Practices 1. **Consistent Naming**: Use predictable thread ID patterns 2. **Meaningful Tags**: Choose tags that aid in filtering and analysis 3. **Session Management**: Tie thread IDs to user sessions 4. **Unique IDs**: Use UUIDs or composite keys to avoid cross-session overlap ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} // Good: Descriptive and unique const threadId = `support-${userId}-${sessionId}-${timestamp}`; // Good: UUID-based for uniqueness const threadId = `conversation-${crypto.randomUUID()}`; // Avoid: Too generic const threadId = "conversation1"; ``` ## Use Cases | Scenario | Thread ID Strategy | Tags Example | | ------------------------ | --------------------------- | ------------------------------ | | **User Sessions** | `user-{userId}-{sessionId}` | `["user-session", "support"]` | | **Support Tickets** | `ticket-{ticketId}` | `["support", "priority-high"]` | | **A/B Testing** | `test-{variant}-{userId}` | `["experiment", "variant-a"]` | | **Multi-Step Workflows** | `workflow-{processId}` | `["onboarding", "step-2"]` | ## Implementation Examples ### Session-Based Threading Assign the same `thread.id` to all requests in a session to group them together in observability. Your application manages the messages array for each request. `thread.id` groups the turns of a single conversation. `orq.session_id` groups requests across conversations for broader session tracking; when a request carries both, the thread ID determines the grouping. See [Orq span attributes](/docs/ai-studio/observability/span-attributes). ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); // Initialize conversation const userId = "user-123"; const sessionId = crypto.randomUUID(); const threadId = `user-${userId}-${sessionId}`; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "How do I reset my password?" }], orq: { thread: { id: threadId, tags: ["support", "password-reset", `user-${userId}`], }, }, }); // Continue conversation const followUp = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [ { role: "user", content: "How do I reset my password?" }, { role: "assistant", content: response.choices[0].message.content }, { role: "user", content: "I didn't receive the reset email" }, ], orq: { thread: { id: threadId, // Same thread ID tags: ["support", "email-issue", `user-${userId}`], }, }, }); ``` ### Multi-Language Support ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Explain machine learning basics", "orq": { "thread": { "id": "tech-discussion-001", "tags": ["education", "ml-basics", "beginner"] } } }' ``` ```bash cURL (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/chat/completions \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [{"role": "user", "content": "Explain machine learning basics"}], "orq": { "thread": { "id": "tech-discussion-001", "tags": ["education", "ml-basics", "beginner"] } } }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const threadId = `conversation-${crypto.randomUUID()}`; const response = await client.responses.create({ model: "openai/gpt-5.6-sol", input: "Explain machine learning basics", orq: { thread: { id: threadId, tags: ["education", "ml-basics", "beginner"], }, }, }); console.log(response.output_text); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI import uuid client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) thread_id = f"conversation-{uuid.uuid4()}" response = client.responses.create( model="openai/gpt-5.6-sol", input="Explain machine learning basics", extra_body={ "orq": { "thread": { "id": thread_id, "tags": ["education", "ml-basics", "beginner"], } } }, ) print(response.output_text) ``` ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const threadId = `conversation-${crypto.randomUUID()}`; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Explain machine learning basics" }], orq: { thread: { id: threadId, tags: ["education", "ml-basics", "beginner"], }, }, }); ``` ```python Python (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import os from openai import OpenAI import uuid client = OpenAI( api_key=os.environ["ORQ_API_KEY"], base_url="https://my.orq.ai/v3/router", ) thread_id = f"conversation-{uuid.uuid4()}" response = client.chat.completions.create( model="openai/gpt-5.6-sol", messages=[{"role": "user", "content": "Explain machine learning basics"}], extra_body={ "orq": { "thread": { "id": thread_id, "tags": ["education", "ml-basics", "beginner"], } } }, ) ``` ## Advanced Patterns ### Dynamic Thread Management ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); interface ThreadConfig { userId: string; sessionType: "support" | "sales" | "general"; priority?: "low" | "medium" | "high"; } function createThreadId(config: ThreadConfig): string { const timestamp = Date.now(); return `${config.sessionType}-${config.userId}-${timestamp}`; } function generateTags(config: ThreadConfig): string[] { const baseTags = [config.sessionType, `user-${config.userId}`]; if (config.priority) { baseTags.push(`priority-${config.priority}`); } return baseTags; } const threadConfig: ThreadConfig = { userId: "user123", sessionType: "support", priority: "high", }; const response = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Urgent: System not responding" }], orq: { thread: { id: createThreadId(threadConfig), tags: generateTags(threadConfig), }, }, }); ``` ### Batch Thread Processing ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); const conversations = [ { userId: "user1", message: "Question about billing" }, { userId: "user2", message: "Technical support needed" }, { userId: "user3", message: "Feature request" }, ]; const responses = await Promise.all( conversations.map(async (conv, index) => { return client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: conv.message }], orq: { thread: { id: `batch-${Date.now()}-${index}`, tags: ["batch-processing", `user-${conv.userId}`], }, }, }); }), ); ``` ### Conversation Flow Tracking ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.ORQ_API_KEY, baseURL: "https://my.orq.ai/v3/router", }); // Track conversation progression const conversationSteps = [ "greeting", "problem-identification", "solution-proposal", "confirmation", ]; async function processConversationStep(step: string, threadId: string, message: string) { return await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: message }], orq: { thread: { id: threadId, tags: ["workflow", `step-${step}`, "customer-service"], }, }, }); } ``` ## Troubleshooting | Issue | Cause | Solution | | ------------------------------ | -------------------------------------------- | --------------------------------------------------------------------------- | | Requests not grouping together | Different thread IDs used across requests | Use a consistent thread ID for all requests in the same session or workflow | | Thread overlap | Non-unique thread IDs shared across sessions | Use UUID or timestamp-based IDs | # AI Gateway traces Source: https://docs.orq.ai/docs/ai-gateway/traces Inspect every AI Gateway request as a detailed trace. View latency, token usage, cache hits, and provider responses for each routed call. ## What are Traces **Traces** provide visibility into the workflow of each model generation and reveal the inner workings of an LLM call on **the AI Gateway**. Traces correspond to events within the generations, following each call to the model configured in the **AI Gateway**. For full observability across **Agents**, **Deployments**, and **Threads** beyond the **AI Gateway**, see the [AI Observability Quickstart](/docs/ai-studio/observability/quickstart). ### Use Cases * **Monitor Performance**: Identify bottlenecks by checking which operations take the most time. Optimize prompts or model selections accordingly. * **Track Costs**: See exactly which operations are consuming tokens and costing money. Understand the cost breakdown across different models and operations. * **Debug Issues**: When something goes wrong, traces show exactly where in the request pipeline the failure occurred, helping identify root causes. * **Optimize Routing**: For AI applications using model routing, traces show which models were selected and how the routing logic performed. * **Analyze Request Flow**: Understand how requests are processed by seeing the complete operation hierarchy and dependencies. ## Viewing Traces To view Traces, go to **Traces** in the **AI Gateway** sidebar. Traces table showing request rows with model, status, latency, and cost columns. The following details are available to visualize: * **Request Timeline**: A hierarchical breakdown of all operations that occurred during the request, from routing decisions to model invocations. * **Operation Details**: Each step in the trace shows: * Model used and provider information. * Token consumption (input/output). * Cost for that specific operation. * Status and any error information. * **Request Metadata**: * Unique trace ID for tracking. * Total request duration. * Aggregated token usage and cost. ## Filtering Traces You can filter traces to find specific requests or focus on particular aspects of AI Gateway calls: | Filter | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Model** | Filter by specific models used (e.g., gpt-5.6-sol, claude-sonnet-5) | | **Provider** | Filter by provider (e.g., OpenAI, Anthropic, Google) | | **Status** | Filter by request status (Success, Error, etc.) | | **Messages** | Filter by the number of iterations in the agent's execution loop, not the number of user or assistant messages. A value of 1 means the model answered directly; each sequential tool round adds one. Parallel tool calls within a single round do not increase the count | | **Cost Range** | Filter traces by cost (minimum and maximum values) | | **Duration** | Filter by request execution duration | | **Date Range** | Filter traces by when they were created | | **Custom Attributes** | Filter by metadata or custom attributes attached to requests | The Messages count is calculated only for Agents running on **Orq.ai**. Traces imported from external frameworks have no value unless the instrumentation sends a numeric `agent.iterations.count` attribute. To apply filters, use the **button**. Combine multiple filters to narrow down your results. ## Managing Columns Show and hide columns to display the most relevant data. **To customize columns:** * Look for the **button** in the traces table header * Toggle columns on or off to show/hide specific data such as: * Model, Provider, Status * Token usage (input/output) * Cost, Duration, Latency * Trace ID, Timestamp * Custom metadata fields ## Creating Custom Views Save frequently used filter combinations as reusable views that can be shared across the team. **To create a custom view:** 1. **Set the desired filters** - Apply the desired filters (e.g., filter by model, status, date range) 2. Click All Rows (top right) 3. Select Create New View 4. **Give the view a title** - Enter a descriptive name (e.g., "GPT-5.6 Sol Errors", "High-Cost Requests") 5. Choose **Make this view private** to keep this view personal (not shared with team members). 6. **Save** - Your filtered view is now created and accessible Click All Rows at the top right to see all available views. Select any saved view to instantly apply those filters and see the relevant traces. ## Reference Complete reference for all `orq.*` span attributes in **AI Gateway** traces, webhook payloads, and trace exports. # Models Source: https://docs.orq.ai/docs/ai-gateway/using-the-router Browse available LLM models and enable them in the AI Gateway. Filter by provider, capability, and pricing to find the right model. ## Enabling new Models To see available Models and enable them for use, navigate to the **Models** page in **AI Gateway**. Models page listing available LLM models with columns for name, input and output pricing, feature badges, release date, max output tokens, context length, and location. Each model displays its full name alongside a set of sortable columns: * **Name**: full model name and provider * **Input / Output pricing**: per-token cost for input and output * **Features**: capability badges indicating support for ZDR, BYOK, and other model-specific features * **Released**: the model's release date * **Max Output Tokens**: maximum tokens the model can generate per response * **Context Length**: total token window (input + output) * **Location**: the region where the model is served Use Sort: Newest to reorder by **Newest**, **Pricing** (low to high or high to low), **Context** (low to high or high to low), or **Max Output Tokens**. Use Columns to show or hide individual columns. Use the **Status Toggle** to **Enable** a model for use with the **AI Gateway**. ### Filters Use the modality tabs at the top of the list to scope models by type: All Text Image Audio Speech Embedding Moderation Rerank The sidebar provides additional filters: | Filter | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Location** | Filter by region: Europe, United States, Global, APAC, Australia, Singapore | | **Access** | Toggle [**Zero data retention**](/docs/enterprise/sovereign-ai#zero-data-retention) for ZDR-compliant providers, or [**BYOK**](/docs/ai-gateway/providers-overview) for providers where an API key has been added | | **Providers** | Filter by LLM provider. See [Providers](/docs/ai-gateway/providers-overview) to configure API keys | | **Status** | Show Enabled or Disabled models | | **Features** | Filter by capability: Base64, Code Execution, Image Edit, JSON Mode, PDF, Reasoning, Streaming, Tool Calling, URL, Vision, Web Search | | **Context length** | Drag the range slider to filter by context window size (512 to 2M tokens) | | **Owner** | Filter between Public (**Orq.ai**-provided) and Private (onboarded) models | To enable a model, toggle it on. It will immediately be available to call with the **AI Gateway**. ### Enable or Disable Models via the API Models can also be enabled and disabled programmatically using the **Models** API. This is useful for CI/CD pipelines, automation scripts, or infrastructure-as-code workflows. These endpoints sit on the [management plane](/docs/management-apis/overview) and authenticate with a [Management Key](/docs/ai-studio/organization/management-keys) that has `workspace-model` write access. A standard API Key cannot be granted the `workspace-model` domain and is rejected with **403**. Enable a model: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --request POST \ --url https://my.orq.ai/v2/workspace-models \ --header "Authorization: Bearer $ORQ_MANAGEMENT_KEY" \ --header "Content-Type: application/json" \ --data '{ "model_id": "openai/gpt-4o" }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_MANAGEMENT_KEY }); await orq.models.enable({ modelId: "openai/gpt-4o" }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ["ORQ_MANAGEMENT_KEY"]) orq.models.enable(model_id="openai/gpt-4o") ``` Disable a model: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --request DELETE \ --url https://my.orq.ai/v2/workspace-models/openai/gpt-4o \ --header "Authorization: Bearer $ORQ_MANAGEMENT_KEY" ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_MANAGEMENT_KEY }); await orq.models.disable({ modelId: "openai/gpt-4o" }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ["ORQ_MANAGEMENT_KEY"]) orq.models.disable(model_id="openai/gpt-4o") ``` Both endpoints return **204** on success. Re-enabling an already-enabled model or disabling an already-disabled model is idempotent and also returns **204**. When **Enforce enabled models** is turned on in [General Settings](/docs/ai-studio/organization/workspace-settings), only models enabled through the dashboard or this API are available for routing. Requests that reference a non-enabled model are rejected. For full request and response schemas, see [Enable model for workspace](/reference/models/enable-model-for-workspace) and [Disable model for workspace](/reference/models/disable-model-for-workspace). ### Restrict Model Access by Project Once a model is enabled, workspace admins can see an **Access control** icon next to it. Select it to choose which projects can use the model, in one of two modes: * **All projects** (default): every project in the workspace can use the model. * **Custom**: each project gets its own on/off toggle, letting admins grant or revoke access per project. Changes save immediately; there is no separate save action. Access control is admin-only. Members with other roles see only the models an admin has approved within their chosen projects. ## Onboarding Private Models Onboard private models by choosing Model at the top-right of the screen. This is useful when hosting a fine-tuned model or any model deployed on a private provider such as **Azure AI Foundry** or **Vertex AI**. ### Private Models Providers From the [**Azure AI Foundry**](https://ai.azure.com/) project homepage, copy the **API key** and one of the two endpoints shown at the top. **Orq.ai** accepts the following endpoints: | Endpoint type | Format | What it imports | | --------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------- | | Azure OpenAI endpoint | `https://.openai.azure.com/openai/v1` | OpenAI-compatible deployments | | Project endpoint | `https://.services.ai.azure.com/api/projects/` | All deployments from publishers in the project (Anthropic, Cohere, and xAI) | Paste the endpoint URL exactly as shown in **Azure AI Foundry**. **Orq.ai** does not append any path suffix. To learn more about the Azure AI Foundry deployment, see our [Provider Documentation](/docs/ai-studio/integrations/providers/azure). In the **AI Gateway** sidebar, go to **Models**, then click Model at the top-right and select **Azure**. Enter the **Base URL** and **API Key**. Azure AI Foundry credentials panel showing the API key, Project endpoint, and Azure OpenAI endpoint at the top of the project homepage. Azure private model configuration form showing endpoint URL and API Key fields. Click **Fetch deployments** to automatically import all available deployments. The imported models appear in the **Models** list. Toggle each model **Enabled** before use. Enabled models are available for routing requests through [Routing Rules](/docs/ai-gateway/configuration/routing-rules). Reference imported models in code using `@azure/` (see [Referencing Private Models in Code](#referencing-private-models-in-code)). In the **AI Gateway** sidebar, go to **Models**, then click Model at the top-right and select **Vertex AI**. Enter the JSON configuration from your Google Cloud project to make the model available on the platform. Google Vertex AI model configuration form showing the JSON configuration input to add a private model. For full Vertex AI setup instructions, see [Google Vertex AI](/docs/ai-studio/integrations/providers/vertex-ai). To import LiteLLM models, first create an [Integration](/docs/ai-studio/integrations/providers/litellm) for the LiteLLM instance. After creation, return to the **AI Gateway** and import models from the connected instance. LiteLLM model import screen showing how to select and import models from a connected LiteLLM instance. ### Referencing Private Models in Code When referencing private models through the SDKs, API, or [Supported Libraries](/docs/ai-studio/integrations/frameworks/overview), the model is referenced by the following string: `@/`. > Example: `corp@azure/gpt-5.6-sol` ## Bring Your Own Key (BYOK) To start using models, connect provider API keys via [**BYOK**](/docs/ai-gateway/providers-overview) in the **AI Gateway** sidebar. # Playground Source: https://docs.orq.ai/docs/ai-studio/ai-chat/using-the-playground Chat and test AI models in real time directly in Orq.ai, with full conversation history, file attachments, and access to configured agents. Playground with the chat list in the sidebar and an open conversation about model context windows **Playground** gives teams one interface to chat with every model and agent in a workspace. No separate subscriptions or accounts needed. * **Every provider, one place**: OpenAI, Anthropic, Google, Mistral, custom models, all in a single chat. * **Switch models mid-conversation**: compare outputs across providers without leaving the thread. * **Chat with agents**: deploy agents built in Orq.ai directly into Playground for the whole team. See [Build Agents](/docs/ai-studio/ai-engineering/build-agents) to learn how to enable agents for Playground. * **Admin-controlled access**: scope available models and agents per workspace. * **Files built in**: upload images, PDFs, and documents directly into the conversation. ## Starting a Chat 1. Click the **Playground** toggle at the top of the sidebar. 2. Click New Chat in the sidebar to start a fresh conversation. 3. Select a model or agent using the selector in the input bar. 4. Type a message in the input box at the bottom. 5. Press **Send** or hit `Enter` to submit. Past conversations appear in the left sidebar, named automatically based on the content of the exchange. Hover over any chat name and click to rename or delete it. ## Models and Agents The input bar includes a selector that lists all models and agents enabled by the workspace admin. Select the desired model or agent before sending a message. Models can be switched mid-conversation. * **Models** are sourced from the [AI Gateway](/docs/ai-gateway/using-the-router), giving access to models across all configured providers. * **Agents** are built in [AI Studio](/docs/ai-studio/ai-engineering/build-agents) around specific use cases and can use tools, knowledge bases, and multi-step reasoning. **Playground** is a great way to test and validate agents before rolling them out to the team. ## Input Options Click in the input bar to open the options menu. Three options are available depending on the selected model or agent: Agents are pre-configured with their own model, tools, and settings. Thinking and Web Search cannot be toggled from the chat. ### Upload Photos & Files Attach images, PDFs, and documents directly to a message. Maximum file size is 20 MB. Available for any model with vision or document capabilities. ### Web Search Only available for models that support Web Search. Toggle web search on or off. When on, the model retrieves up-to-date information from the web to ground its responses. ### Thinking Only available for models that support Thinking. When Thinking is enabled, the model reasons through the problem before writing its response. This improves accuracy on complex or multi-step tasks at the cost of additional tokens and latency. Selecting **Thinking** opens a submenu with two modes: * **Standard**: the model applies a moderate level of reasoning. * **Extended**: the model reasons more deeply, best suited for difficult problems that benefit from more thorough analysis. # Build Agents Source: https://docs.orq.ai/docs/ai-studio/ai-engineering/build-agents Build AI agents in Orq.ai: set instructions, pick models, and attach tools, knowledge bases, memory, and guardrails via AI Studio, the API, or Orq MCP. Configure every aspect of an agent before execution. For running agents, see [Run Agents](/docs/ai-studio/ai-engineering/run-agents). Common use cases include customer support assistants, RAG-powered document Q\&A, coding assistants, research and data extraction pipelines, and any multi-step AI workflow that needs tools, memory, and knowledge bases. ## Create an Agent **AI Studio** is the visual interface for building, configuring, and testing AI agents without writing code. Open [the AI Studio](https://my.orq.ai). Open **Agents** in the **Managed Agents** section, then click Agent. New entity menu listing Folder, Agent (Beta), Dataset, Deployment, Evaluator, Experiment, Knowledge, Playground, Prompt, Snippet, and Tool. Name and describe the Agent. Use the AI assistant to pre-configure role and instructions, or choose **Start from scratch** for full manual control. Create Agent dialog with fields for Agent Key, Agent Name, and a description field, and options to Create with AI or Start from scratch. AI Studio opens with a customizable template. AI Studio has three panels: AI Studio showing the three-panel layout: Instructions panel on the left, Configuration panel in the center, and Chat panel on the right. * **Instructions Panel (left)**: Define what the agent does and how it behaves. * **Configuration Panel (center)**: Set up model, tools, context, evaluators, and constraints. * **Chat Panel (right)**: Chat with the agent and test their behaviour. Save the configuration at any time using the Publish button. The Agents API provides endpoints for creating, executing, and managing AI agents with support for tools, memory, knowledge bases, and real-time streaming. Payloads follow the [A2A Protocol](https://a2a-protocol.org/latest/). **Prerequisites:** * A Project in the workspace (used as the `path` for resources) * An [API Key](/docs/ai-studio/organization/api-keys) * Optionally, one of the [Orq SDKs](/reference/client-libraries) Create an agent with a minimal configuration: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/agents \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "my-agent", "role": "Assistant", "description": "A helpful assistant for general tasks", "instructions": "Be helpful and concise", "path": "Default/agents", "model": { "id": "openai/gpt-5.6-sol", "parameters": { "temperature": 1 } }, "settings": { "max_iterations": 3, "max_execution_time": 300, "tools": [] } }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os with Orq(api_key=os.getenv("ORQ_API_KEY", "")) as orq: agent = orq.agents.create( key="my-agent", role="Assistant", description="A helpful assistant for general tasks", instructions="Be helpful and concise", path="Default/agents", model={"id": "openai/gpt-5.6-sol", "parameters": {"temperature": 1}}, settings={ "max_iterations": 3, "max_execution_time": 300, "tools": [] } ) print(f"Agent created: {agent.key}") ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from '@orq-ai/node'; const orq = new Orq({ apiKey: process.env['ORQ_API_KEY'] ?? '' }); const agent = await orq.agents.create({ key: 'my-agent', role: 'Assistant', description: 'A helpful assistant for general tasks', instructions: 'Be helpful and concise', path: 'Default/agents', model: { id: 'openai/gpt-5.6-sol', parameters: { temperature: 1 } }, settings: { maxIterations: 3, maxExecutionTime: 300, tools: [] } }); console.log(`Agent created: ${agent.key}`); ``` See the full [Create Agent API reference](/reference/agents/create-agent). Use the [Orq MCP server](/docs/ai-studio/integrations/code-assistants/orq-mcp) to manage agents directly from an AI code assistant. **Find an existing agent:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Search for the "support-bot" agent in my workspace ``` The assistant uses `search_entities` with `type: "agent"` to locate agents by name or key. *** **Retrieve full agent configuration:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Get the full configuration of the "support-bot" agent ``` The assistant uses `get_agent` to return instructions, model, tools, and all settings. *** **Create an agent:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Create a customer support agent called "support-bot" in the Default/agents project. Use GPT-5.6 Sol with temperature 1 and give it a professional, concise tone. ``` The assistant uses `create_agent` with the specified key, path, model, model parameters, and instructions. Create an agent with a minimal configuration using [`orq agents create`](/reference/cli): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents create \ --key my-agent \ --role Assistant \ --description "A helpful assistant for general tasks" \ --instructions "Be helpful and concise" \ --path Default/agents \ --model '{"id": "openai/gpt-5.6-sol", "parameters": {"temperature": 1}}' \ --settings '{"max_iterations": 3, "max_execution_time": 300, "tools": []}' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents create --help` for the full flag reference. ## Select a Model Select the language model that powers the agent from the Configuration panel. Available models depend on the [AI Gateway](/docs/ai-gateway/using-the-router) configuration. Switch models at any time and the agent uses the new model on its next execution. Access the model parameters sub-menu to configure temperature and other parameters. **Considerations when selecting a model:** * **Speed vs Quality**: Smaller models are faster but less capable. * **Cost**: Larger models cost more per token. * **Capability**: Some tasks require more advanced reasoning models. * **Latency**: Models that use reasoning tokens add latency. Consider the impact of Max Iterations and Max Execution Time constraints. The `model` field supports two formats. **Object format (recommended)**: Specify model parameters alongside the model ID. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} "model": { "id": "openai/gpt-5.6-luna", "parameters": { "temperature": 1 } } ``` **String format**: For simple cases without custom parameters. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} "model": "openai/gpt-5.6-luna" ``` Use the `provider/model-id` format. For a complete list of supported models, see the [AI Gateway](/docs/ai-gateway/using-the-router). See the full [API reference](/reference/agents/create-agent) for all supported model parameters. **List available models:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} List all available chat models in my workspace ``` The assistant uses `list_models` with `type: "chat"`. *** **Update the model on an existing agent:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Switch the "support-bot" agent to use GPT-5.6 Luna ``` The assistant uses `update_agent` with `model: { "id": "openai/gpt-5.6-luna" }`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --model '{"id": "openai/gpt-5.6-luna", "parameters": {"temperature": 1}}' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ## Configure Instructions The **Instructions** panel defines the agent's behavior, goals, and personality. Write clear, exhaustive instructions to keep behavior consistent across executions. Use the AI button to generate effective instructions for the agent. ``` You are an experienced customer support specialist for the SaaS company **{company_name}**. Your job is to provide clear, concise, and accurate answers to customer inquiries about {product_name}. Responses should be brief, no more than **150 words**, and include any necessary next-step actions. **Step-by-Step Instructions** 1. **Read the query**: `{customer_query}`. 2. **Extract the core problem** (e.g., password reset, API error, pricing). 3. **Draft a concise answer**: no more than 150 words. 4. **Add suggested next steps**: at most 3 actions the customer can take. 5. **End with a friendly closing** and a reminder of available support channels (`{support_contact}`). ``` The key instruction fields on the agent object: | Field | Description | | --------------- | ------------------------------------------------------------ | | `instructions` | Main instructions for the agent's behavior and goals | | `role` | Agent's responsibility and coverage, reinforced at execution | | `description` | Used by other agents to discover and delegate to this agent | | `system_prompt` | Additional system-level context injected before execution | Update instructions on an existing agent: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X PATCH https://my.orq.ai/v2/agents/my-agent \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "instructions": "You are a helpful assistant. Be concise and accurate.", "role": "General Assistant", "description": "A general-purpose assistant for answering questions and completing tasks" }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os with Orq(api_key=os.getenv("ORQ_API_KEY", "")) as orq: agent = orq.agents.update( agent_key="my-agent", instructions="You are a helpful assistant. Be concise and accurate.", role="General Assistant", description="A general-purpose assistant for answering questions and completing tasks" ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from '@orq-ai/node'; const orq = new Orq({ apiKey: process.env['ORQ_API_KEY'] ?? '' }); await orq.agents.update({ instructions: 'You are a helpful assistant. Be concise and accurate.', role: 'General Assistant', description: 'A general-purpose assistant for answering questions and completing tasks' }, 'my-agent'); ``` See the full [Update Agent API reference](/reference/agents/update-agent). **Update agent instructions:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Show me the current instructions for "support-bot", then update them to always respond in the user's language ``` The assistant uses `get_agent` to retrieve the current instructions, then `update_agent` with the revised `instructions` field. *** **Set role and description:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Update the "support-bot" agent to add a detailed description of its capabilities for use in multi-agent workflows ``` The assistant uses `update_agent` with the `description` field. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --instructions "You are a helpful assistant. Be concise and accurate." \ --role "General Assistant" \ --description "A general-purpose assistant for answering questions and completing tasks" ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ### Role and Description * **Role**: Defines the agent's responsibility and coverage. Sent to the agent during execution to reinforce its perimeter. * **Description**: Used by other agents in multi-agent setups to understand what this agent can do. Write a detailed description so orchestrators delegate correctly. To learn more about multi-agent orchestration, see [Multi-Agent Workflows](/docs/ai-studio/ai-engineering/run-agents#multi-agent-workflows). ### Skills [Skills](/docs/ai-studio/ai-engineering/skills) can be used with agents in two ways: **Static reference:** Use `{{skill.key}}` directly in the instruction text to inject a Skill's content at that position on every run. Use this for content that should always be present, such as a company policy or a standard output format. **On-demand attachment:** Click Skills in the **Skills** section of the agent configuration to attach a Skill. Agent configuration panel showing a skill being attached in the Skills section. Attached Skills are available for the agent to invoke when relevant, without being statically embedded in the instructions. Any update to a Skill propagates automatically to every agent that references or has it attached. ### Variables and Templates Reference dynamic values in agent instructions using double braces: `{{variableName}}`. Pass a key-value map in the `variables` field at invocation time and **Orq.ai** substitutes each variable before execution. AI Studio editor showing instructions with a {{language}} variable highlighted in green. **Orq.ai** supports three template engines. Select the **Template Engine** from the Agent Settings panel: * **Text** (default): variables use `{{double_braces}}` syntax. * **Jinja**: full templating with conditionals, loops, filters, and more. * **Mustache**: logic-less templating with sections. Template Engine dropdown with Text currently selected, and options for Jinja and Mustache. **Instructions template:** ```jinja theme={"theme":{"light":"github-light","dark":"github-dark"}} You are a support assistant for {{company_name}}. {% if user_tier == "premium" %} {{customer_name}} is a premium customer. Greet them by name and let them know they have priority support. {% else %} {{customer_name}} is on the free plan. Standard response time is 24 hours. {% endif %} ``` **Invoke the agent:** ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "agent/support-bot", "input": "I need help.", "variables": { "company_name": "Acme", "customer_name": "Sarah", "user_tier": "premium" } }' ``` **Instructions template:** ```handlebars theme={"theme":{"light":"github-light","dark":"github-dark"}} You are a support assistant for {{company_name}}. {{# is_premium}} {{customer_name}} is a premium customer. Priority support with a 2-hour SLA. {{/ is_premium}} {{^ is_premium}} {{customer_name}} is on the free plan. Standard response time is 24 hours. {{/ is_premium}} ``` **Invoke the agent:** ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "agent/support-bot", "input": "I need help.", "variables": { "company_name": "Acme", "customer_name": "Sarah", "is_premium": true } }' ``` For a complete reference of all template features including filters, macros, and more, see [Prompt Templating](/docs/ai-studio/prompts/prompt-templating). To test declared variables without calling the API, use the Chat Panel's Variables button. See [Test in the Chat Panel](#test-in-the-chat-panel) below. Use `{{variableName}}` placeholders in agent instructions and pass the corresponding values in the `variables` field at invocation time. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl --request POST \ --url 'https://my.orq.ai/v3/router/responses' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "model": "agent/my-agent", "input": "I need help with my account.", "variables": { "user_name": "John Smith", "user_role": "admin", "company_name": "Acme Corp" } }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os with Orq(api_key=os.getenv("ORQ_API_KEY", "")) as orq: response = orq.responses.create( model="agent/my-agent", input="I need help with my account.", variables={ "user_name": "John Smith", "user_role": "admin", "company_name": "Acme Corp", }, ) print(response.output[0]["content"][0]["text"]) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from '@orq-ai/node'; const orq = new Orq({ apiKey: process.env['ORQ_API_KEY'] ?? '' }); const response = await orq.responses.create({ model: 'agent/my-agent', input: 'I need help with my account.', variables: { user_name: 'John Smith', user_role: 'admin', company_name: 'Acme Corp', }, }); console.log(response.output?.[0]?.content?.[0]?.text); ``` See the full [Create Response API reference](/reference/responses/create-response). **Add variables to agent instructions:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Update "support-bot" instructions to use {{user_name}}, {{user_role}}, and {{company_name}} variables ``` The assistant uses `update_agent` with the revised `instructions` field containing `{{variable}}` placeholders. **Step 1 — add `{{variable}}` placeholders to the agent's instruction template:** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --instructions "You are a helpful assistant for {{user_name}} ({{user_role}}) at {{company_name}}. Be concise and accurate." ``` **Step 2 — supply variable values at runtime:** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq responses create \ --model agent/my-agent \ --input '"I need help with my account."' \ --variables user_name="John Smith" --variables user_role=admin --variables company_name="Acme Corp" ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` and `orq responses create --help` for the full flag reference. ## Add Tools Tools extend the agent's capabilities by allowing it to interact with external systems, execute code, or fetch information. Add tools from the Tool selection modal. Tool library showing available tools including Web search, Current date, Write memory store, Delete memory document, http_tool, python_tool, and others. Declare tools in the `settings.tools` array when creating or updating an agent. **Add a standard tool to an agent:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Add the google_search and current_date tools to the "research-bot" agent ``` The assistant uses `update_agent` with the updated `settings.tools` array. *** **Add a custom tool by key:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Add the HTTP tool with key "weather_api" to the "weather-bot" agent ``` The assistant uses `update_agent` with `{"type": "http", "key": "weather_api"}` in `settings.tools`. Declare tools in the `settings.tools` array using [`orq agents update`](/reference/cli): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "google_search"}, {"type": "current_date"}]}' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ### Standard Tools The following tools are available to all agents out of the box: | Tool | Name | Description | | ------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Current Date | `current_date` | Provides the current date to the model | | Web Search | `google_search` | Lets an agent perform a web search | | Web Scraper | `web_scraper` | Lets an agent scrape a web page | | Query Memory Store | `query_memory_store` | Lets an agent query a [Memory Store](/docs/ai-studio/ai-engineering/build-agents#connect-memory-stores). Added automatically when using a Memory Store. | | Retrieve Memory Stores | `retrieve_memory_stores` | Lets an agent fetch [Memory Stores](/docs/ai-studio/ai-engineering/build-agents#connect-memory-stores). Added automatically when using a Memory Store. | | Write Memory Store | `write_memory_store` | Lets an agent save to a [Memory Store](/docs/ai-studio/ai-engineering/build-agents#connect-memory-stores). Added automatically when using a Memory Store. | | Delete Memory Document | `delete_memory_document` | Lets an agent delete a memory document. Added automatically when using a Memory Store. | | Query Knowledge Base | `query_knowledge_base` | Lets an agent query a [Knowledge Base](/docs/ai-studio/ai-engineering/build-agents#connect-knowledge-bases). Added automatically when using a Knowledge Base. | | Retrieve Knowledge Bases | `retrieve_knowledge_bases` | Lets an agent fetch a [Knowledge Base](/docs/ai-studio/ai-engineering/build-agents#connect-knowledge-bases). Added automatically when using a Knowledge Base. | | Call Sub Agent | `call_sub_agent` | Lets an agent invoke another agent. | | Retrieve Agents | `retrieve_agents` | Lets an agent fetch other agents. | | Advisor | `advisor` | Lets an agent consult a second, more capable model for advice. See [Advisor and Sidekick Tools](/docs/ai-studio/ai-engineering/build-agents#advisor-and-sidekick-tools). | | Sidekick | `sidekick` | Lets an agent delegate a self-contained subtask to a second model. See [Advisor and Sidekick Tools](/docs/ai-studio/ai-engineering/build-agents#advisor-and-sidekick-tools). | Create custom tools to use within Agents: * [Function Tool](/docs/ai-studio/ai-engineering/create-tools#function-tool) * [HTTP Tool](/docs/ai-studio/ai-engineering/create-tools#http-tool) * [JSON Schema Tool](/docs/ai-studio/ai-engineering/create-tools#json-schema-tool) * [MCP Servers](/docs/ai-gateway/mcp-portal/mcp-servers) * [Python Tool](/docs/ai-studio/ai-engineering/create-tools#python-tool) Agent instructions must **explicitly mention the available tools** so the model knows when and how to invoke them. The model will not use tools unless the instructions clearly describe: * What each tool does * When to use it * How to call it **Example: Web Search Agent** ``` You are a research assistant. Your job is to find current information. **Available Tools:** 1. **google_search** - Search the internet for information - Use this when you need current information or factual data - Provide clear search queries - Example: When asked "What are the latest AI developments?", call google_search with "latest AI developments 2025" ``` | Tool | Name | Description | | ------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Current Date | `current_date` | Provides the current date to the model | | Web Search | `google_search` | Lets an agent perform a web search | | Web Scraper | `web_scraper` | Lets an agent scrape a web page | | Query Memory Store | `query_memory_store` | Lets an agent query a [Memory Store](/docs/ai-studio/ai-engineering/build-agents#connect-memory-stores). | | Retrieve Memory Stores | `retrieve_memory_stores` | Lets an agent fetch [Memory Stores](/docs/ai-studio/ai-engineering/build-agents#connect-memory-stores). | | Write Memory Store | `write_memory_store` | Lets an agent save to a [Memory Store](/docs/ai-studio/ai-engineering/build-agents#connect-memory-stores). | | Delete Memory Document | `delete_memory_document` | Lets an agent delete a memory document. | | Query Knowledge Base | `query_knowledge_base` | Lets an agent query a [Knowledge Base](/docs/ai-studio/ai-engineering/build-agents#connect-knowledge-bases). | | Retrieve Knowledge Bases | `retrieve_knowledge_bases` | Lets an agent fetch a [Knowledge Base](/docs/ai-studio/ai-engineering/build-agents#connect-knowledge-bases). | | Call Sub Agent | `call_sub_agent` | Lets an agent invoke another agent. | | Retrieve Agents | `retrieve_agents` | Lets an agent fetch other agents. | | Advisor | `advisor` | Lets an agent consult a second, more capable model for advice. See [Advisor and Sidekick Tools](/docs/ai-studio/ai-engineering/build-agents#advisor-and-sidekick-tools). | | Sidekick | `sidekick` | Lets an agent delegate a self-contained subtask to a second model. See [Advisor and Sidekick Tools](/docs/ai-studio/ai-engineering/build-agents#advisor-and-sidekick-tools). | ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "settings": { "tools": [ { "type": "current_date" }, { "type": "google_search" }, { "type": "web_scraper" }, { "type": "query_memory_store" }, { "type": "retrieve_memory_stores" }, { "type": "write_memory_store" }, { "type": "delete_memory_document" }, { "type": "query_knowledge_base" }, { "type": "retrieve_knowledge_bases" }, { "type": "call_sub_agent" }, { "type": "retrieve_agents" } ] } } ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "current_date"}, {"type": "google_search"}, {"type": "web_scraper"}, {"type": "query_memory_store"}, {"type": "retrieve_memory_stores"}, {"type": "write_memory_store"}, {"type": "delete_memory_document"}, {"type": "query_knowledge_base"}, {"type": "retrieve_knowledge_bases"}, {"type": "call_sub_agent"}, {"type": "retrieve_agents"}]}' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ### Advisor and Sidekick Tools An **Agent** runs every step of a conversation on a single model. That model has to be affordable enough for routine steps, yet capable enough for the hardest reasoning step in the run. Sizing the model for the hardest step makes every step expensive; sizing it for routine steps makes the agent fail exactly when quality matters most. Self-contained subtasks cause a related problem: drafting or summarizing inside the main loop fills the context window with intermediate work the conversation does not need. The Advisor and Sidekick tools solve this by giving the agent a second model, configured at design time, that it can call mid-run: * **Advisor**: the agent consults a more capable model when it is stuck or faces a high-stakes decision. The advisor receives the recent conversation transcript plus the agent's question and returns short, actionable guidance. The agent stays in control and produces the final answer itself. * **Sidekick**: the agent delegates a concrete subtask (drafting, extraction, summarization, transformation) to a second model and receives only the finished result. The sidekick does not see the conversation, so the subtask runs in isolation and the main context stays small. This enables a cost and quality mix inside one agent: run a fast, low-cost executor model and escalate to a stronger model only in the moments that need it, without building a separate sub-agent. For both tools, the secondary model and its parameters are fixed in the tool configuration. The agent only supplies the question or task at run time. Each secondary call is metered separately and appears as a nested span in [Traces](/docs/ai-studio/observability/traces). When the secondary model call fails, the agent receives an error message as the tool result and the run continues. In **AI Studio**, add Advisor or Sidekick from the tool library and open the tool to configure the secondary model and parameters. Via the API, set the fields in the tool's `configuration` object. #### Advisor The advisor receives the newest conversation turns within the `max_transcript_tokens` budget, plus the agent's question and optional context, and replies with concise advice. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "advisor", "configuration": { "model": "openai/gpt-4o", "max_uses": 3, "max_transcript_tokens": 8000 } } ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "advisor", "configuration": {"model": "openai/gpt-4o", "max_uses": 3, "max_transcript_tokens": 8000}}]}' ``` | Field | Required | Description | | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `model` | Yes | Secondary model in `provider/model` format. | | `max_uses` | No | Maximum advisor calls per run. `0` or omitted: unlimited. | | `max_transcript_tokens` | No | Estimated token budget for the conversation transcript sent to the advisor. The newest turn is always included. `0` or omitted: full transcript. | | `max_tokens` | No | Maximum output tokens for the secondary model. `0` or omitted: provider default. | | `temperature` | No | Sampling temperature, `0` to `2`. Omitted: provider default. | | `reasoning_effort` | No | One of `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Omitted: provider default. | #### Sidekick The sidekick receives only the task and optional context the agent supplies, together with the configured system prompt and output format. It returns the finished result and nothing else. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "sidekick", "configuration": { "model": "openai/gpt-5.6-luna", "max_tokens": 1024, "system_prompt": "You are a meticulous copy editor.", "output_format": "a concise bulleted list" } } ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "sidekick", "configuration": {"model": "openai/gpt-5.6-luna", "max_tokens": 1024, "output_format": "a concise bulleted list"}}]}' ``` | Field | Required | Description | | ------------------ | -------- | ------------------------------------------------------------------------------------------------------ | | `model` | Yes | Secondary model in `provider/model` format. | | `max_uses` | No | Maximum sidekick calls per run. `0` or omitted: unlimited. | | `system_prompt` | No | Replaces the default sidekick system prompt. Omitted: platform default. | | `output_format` | No | Free-form guidance for the shape of the result, for example `a concise bulleted list` or `valid JSON`. | | `max_tokens` | No | Maximum output tokens for the secondary model. `0` or omitted: provider default. | | `temperature` | No | Sampling temperature, `0` to `2`. Omitted: provider default. | | `reasoning_effort` | No | One of `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Omitted: provider default. | Mention the tools in the agent instructions so the model knows when to use them, for example: "When unsure about a legal or pricing decision, ask the advisor before answering" or "Delegate document summarization to the sidekick and use its result." The `model` field is required. When the tool is added without a configured model, tool calls return a configuration error to the agent instead of advice or a result. ### Function Tools **Function tools** let the model call code that runs in the application invoking the agent, not on the **Orq.ai** platform. See [Choosing a tool type](/docs/ai-studio/ai-engineering/create-tools#choosing-a-tool-type) for when to use one, [Function Tool](/docs/ai-studio/ai-engineering/create-tools#function-tool) to create one, and [Use Tools](/docs/ai-studio/ai-engineering/run-agents#use-tools) for the tool loop the application runs to execute the call and return the result. Reference a pre-created Function tool by its `key`: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "function", "key": "get_local_events" } ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "function", "key": "get_local_events"}]}' ``` ### Python Tools Reference a pre-created Python tool by its `key`. Create Python tools first via the [Tools page](/docs/ai-studio/ai-engineering/create-tools#python-tool) or API. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "code", "key": "password_generator" } ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "code", "key": "password_generator"}]}' ``` ### HTTP Tools Reference a pre-created HTTP tool by its `key`. Create HTTP tools first via the [Tools page](/docs/ai-studio/ai-engineering/create-tools#http-tool) or API. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "http", "key": "weather_api" } ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "http", "key": "weather_api"}]}' ``` ### MCP Servers Reference a pre-created MCP server by its `tool_id`. Create MCP servers first via the [MCP Portal](/docs/ai-gateway/mcp-portal/mcp-servers) or API. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "mcp", "tool_id": "TOOL_ID" } ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"tools": [{"type": "mcp", "tool_id": "TOOL_ID"}]}' ``` See the full [Create Agent API reference](/reference/agents/create-agent) for all tool configuration options. ## Connect Knowledge Bases Attach a Knowledge Base to ground the agent's responses in relevant data. 1. Click **Add context** in the Configuration panel. 2. Select a [Knowledge Base](/docs/ai-studio/ai-engineering/knowledge-bases). Unlike Deployments, a Knowledge Base attached to an agent is not queried on every request. The agent decides when to use the `query_knowledge_base` tool based on context. The Knowledge Base description must be explicit so the agent knows when to query it. For more on building Knowledge Bases for Agents, see [Knowledge Bases](/docs/ai-studio/ai-engineering/knowledge-bases). Add the `knowledge_bases` array to the agent configuration. Include the `retrieve_knowledge_bases` and `query_knowledge_base` tools so the agent can discover and query them. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/agents \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "knowledge-agent", "instructions": "Help the user. First use retrieve_knowledge_bases to see what knowledge sources are available, then query_knowledge_base to find relevant information.", "path": "Default/agents", "model": { "id": "openai/gpt-5.6-luna" }, "settings": { "max_iterations": 5, "max_execution_time": 600, "tools": [ { "type": "retrieve_knowledge_bases" }, { "type": "query_knowledge_base" } ] }, "knowledge_bases": [ { "knowledge_id": "my_knowledge_base" } ] }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os with Orq(api_key=os.getenv("ORQ_API_KEY", "")) as orq: agent = orq.agents.create( key="knowledge-agent", instructions="Help the user. First use retrieve_knowledge_bases to see what knowledge sources are available, then query_knowledge_base to find relevant information.", path="Default/agents", model={"id": "openai/gpt-5.6-luna"}, settings={ "max_iterations": 5, "max_execution_time": 600, "tools": [ {"type": "retrieve_knowledge_bases"}, {"type": "query_knowledge_base"} ] }, knowledge_bases=[{"knowledge_id": "my_knowledge_base"}] ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from '@orq-ai/node'; const orq = new Orq({ apiKey: process.env['ORQ_API_KEY'] ?? '' }); await orq.agents.create({ key: 'knowledge-agent', instructions: 'Help the user. First use retrieve_knowledge_bases to see what knowledge sources are available, then query_knowledge_base to find relevant information.', path: 'Default/agents', model: { id: 'openai/gpt-5.6-luna' }, settings: { maxIterations: 5, maxExecutionTime: 600, tools: [ { type: 'retrieve_knowledge_bases' }, { type: 'query_knowledge_base' } ] }, knowledgeBases: [{ knowledgeId: 'my_knowledge_base' }] }); ``` Agents must use `retrieve_knowledge_bases` before querying. Guide the agent with instructions like: "First use retrieve\_knowledge\_bases to see what knowledge sources are available, then query\_knowledge\_base to find relevant information." See the full [Create Agent API reference](/reference/agents/create-agent) and [Knowledge Bases](/docs/ai-studio/ai-engineering/knowledge-bases) for more details. **Attach a knowledge base to an agent:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Add the knowledge base with ID "product-docs" to the "support-bot" agent and make sure the query and retrieve tools are included ``` The assistant uses `update_agent` with the `knowledge_bases` array and adds `query_knowledge_base` and `retrieve_knowledge_bases` to `settings.tools`. Attach a Knowledge Base to an existing agent using [`orq agents update`](/reference/cli): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --instructions "Help the user. First use retrieve_knowledge_bases to see what knowledge sources are available, then query_knowledge_base to find relevant information." \ --settings '{"max_iterations": 5, "max_execution_time": 600, "tools": [{"type": "retrieve_knowledge_bases"}, {"type": "query_knowledge_base"}]}' \ --knowledge-bases '[{"knowledge_id": "my_knowledge_base"}]' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ## Connect Memory Stores Attach a Memory Store to give the agent persistent memory across conversations. 1. Click **Add context** in the Configuration panel. 2. Select a [Memory Store](/docs/ai-studio/ai-engineering/memory-stores). Memory Stores are created and managed through the API. To learn more, see [Using Memory Stores](/docs/ai-studio/ai-engineering/memory-stores). To use a Memory Store correctly, a **Memory Entity ID** must be sent during agent execution. This entity ID scopes memories to a specific user or session. For more on using Memory Stores with Agents, see the [Memory Stores](/docs/ai-studio/ai-engineering/memory-stores) documentation. Add the `memory_stores` array to the agent configuration. Include the memory tools so the agent can discover, query, write, and delete memories. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/agents \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "memory-agent", "instructions": "You have access to user memories. Use retrieve_memory_stores to find what stores are available, then query_memory_store to search for relevant information before responding.", "path": "Default/agents", "model": "openai/gpt-5.6-luna", "settings": { "max_iterations": 5, "max_execution_time": 300, "tools": [ { "type": "retrieve_memory_stores" }, { "type": "query_memory_store" }, { "type": "write_memory_store" }, { "type": "delete_memory_document" } ] }, "memory_stores": ["customer_information"] }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os with Orq(api_key=os.getenv("ORQ_API_KEY", "")) as orq: agent = orq.agents.create( key="memory-agent", instructions="You have access to user memories. Use retrieve_memory_stores to find what stores are available, then query_memory_store to search for relevant information before responding.", path="Default/agents", model="openai/gpt-5.6-luna", settings={ "max_iterations": 5, "max_execution_time": 300, "tools": [ {"type": "retrieve_memory_stores"}, {"type": "query_memory_store"}, {"type": "write_memory_store"}, {"type": "delete_memory_document"} ] }, memory_stores=["customer_information"] ) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from '@orq-ai/node'; const orq = new Orq({ apiKey: process.env['ORQ_API_KEY'] ?? '' }); await orq.agents.create({ key: 'memory-agent', instructions: 'You have access to user memories. Use retrieve_memory_stores to find what stores are available, then query_memory_store to search for relevant information before responding.', path: 'Default/agents', model: 'openai/gpt-5.6-luna', settings: { maxIterations: 5, maxExecutionTime: 300, tools: [ { type: 'retrieve_memory_stores' }, { type: 'query_memory_store' }, { type: 'write_memory_store' }, { type: 'delete_memory_document' } ] }, memoryStores: ['customer_information'] }); ``` Memory stores do not automatically save all information from conversations. Explicitly instruct the agent what to save. Without clear save instructions, the agent may miss important details. Pass a `memory.entity_id` at execution time to scope memories to a specific user or session. See [Run Agents](/docs/ai-studio/ai-engineering/run-agents#use-memory-stores) for execution details. **Attach a memory store to an agent:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Add the "customer_information" memory store to the "support-bot" agent and include all four memory tools ``` The assistant uses `update_agent` with the `memory_stores` array and the four memory tools in `settings.tools`. Attach a Memory Store to an existing agent using [`orq agents update`](/reference/cli): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --instructions "You have access to user memories. Use retrieve_memory_stores to find what stores are available, then query_memory_store to search for relevant information before responding." \ --settings '{"max_iterations": 5, "max_execution_time": 300, "tools": [{"type": "retrieve_memory_stores"}, {"type": "query_memory_store"}, {"type": "write_memory_store"}, {"type": "delete_memory_document"}]}' \ --memory-stores customer_information ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ## Configure Evaluators and Guardrails Evaluators measure agent performance against defined criteria. Guardrails can block execution when an evaluation fails. Only pre-configured Evaluators can be attached to agents. To see available standard evaluators or create custom ones, see [Evaluators](/docs/ai-studio/optimize/evaluators). 1. Click **Add Evaluator** or **Add Guardrail** in the Configuration panel. 2. Select the evaluator type. 3. Configure evaluation parameters: * **Input or Output**: whether to evaluate the agent's input or its output. * **Sample Rate** (Evaluators only): the fraction of executions that trigger evaluation. Evaluators run automatically during task execution and provide performance metrics. **Output Guardrails and Streaming**: When an agent is invoked with streaming enabled, output guardrails are deactivated because they cannot run on partial chunks. To learn more, see [Evaluators and Guardrails in Deployments](/docs/ai-studio/ai-engineering/deployments#evaluators-and-guardrails). Attach evaluators and guardrails to an agent using the `evaluators` and `guardrails` fields inside `settings` in the create or update payload. For the full schema of evaluator and guardrail configuration, see the [Create Agent API reference](/reference/agents/create-agent) and [Evaluators](/docs/ai-studio/optimize/evaluators). Use [`PATCH /v2/agents/{key}`](/reference/agents/update-agent) to add or update evaluators and guardrails on an existing agent without recreating it. **Add an evaluator to an agent:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Add the "response-quality" LLM evaluator to the "support-bot" agent, configured for output evaluation ``` The assistant uses `update_agent` with the `evaluators` field. *** **Add a guardrail:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Add a guardrail to "support-bot" that blocks responses when the toxicity evaluator score is above 0.8 ``` The assistant uses `update_agent` with the `guardrails` field. `evaluators` and `guardrails` are fields inside `settings`, reachable through the existing `--settings` flag on [`orq agents update`](/reference/cli): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"evaluators": [{"id": "response-quality", "execute_on": "output", "sample_rate": 100}], "guardrails": [{"id": "toxicity-guardrail", "execute_on": "output", "sample_rate": 100}]}' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ## Configure Runtime Constraints Control resource usage and execution limits from the Configuration panel. | Constraint | Description | | ---------------------- | ------------------------------------------------------- | | **Max Iterations** | Maximum number of LLM reasoning iterations per response | | **Max Execution Time** | Maximum time the agent runs (in seconds) | Max Iterations and Max Execution Time compound: an agent requiring many reasoning steps can hit both limits simultaneously. `max_execution_time` counts only LLM thinking time; tool call and sub-agent call duration is excluded. Start conservative and increase as needed. Agents are run and scaled by **Orq.ai**. No infrastructure setup required. Set constraints in the `settings` object: | Field | Type | Description | | -------------------- | ------- | ------------------------------------------ | | `max_iterations` | integer | Maximum number of LLM reasoning iterations | | `max_execution_time` | integer | Maximum execution time in seconds | ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "settings": { "max_iterations": 5, "max_execution_time": 300 } } ``` Update constraints on an existing agent with `PATCH /v2/agents/{key}`. **Update execution constraints:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Set the max iterations to 10 and max execution time to 600 seconds on the "research-bot" agent ``` The assistant uses `update_agent` with the updated `settings.max_iterations` and `settings.max_execution_time` fields. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq agents update my-agent \ --settings '{"max_iterations": 5, "max_execution_time": 300}' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq agents update --help` for the full flag reference. ## Versions The **Versions** tab shows the full history of all published agent configurations. Open it by selecting **Versions** from the tabs in AI Studio. Versions tab listing v4.1.0 (latest, beta_env), v4.0.0 (production), v3.0.0, and v2.0.0, each with author and timestamp. Each version entry shows the version number, author, timestamp, optional commit message, and any assigned environment badges (e.g. `latest`, `production`). By default, invoking an agent routes to the version tagged **latest**. To target a specific version, append `@version-number` to the agent key. Route by environment with `@environment-name`. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "agent/my-agent@4.0.0", "input": "Hello" }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os with Orq(api_key=os.getenv("ORQ_API_KEY", "")) as orq: response = orq.responses.create( model="agent/my-agent@4.0.0", input="Hello", ) print(response) ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from '@orq-ai/node'; const orq = new Orq({ apiKey: process.env['ORQ_API_KEY'] ?? '' }); const response = await orq.responses.create({ model: 'agent/my-agent@4.0.0', input: 'Hello', }); console.log(response); ``` Use `@environment-name` to route to whichever version is assigned to that environment: ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v3/router/responses \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "agent/my-agent@production", "input": "..." }' ``` See the full [Create Response API reference](/reference/responses/create-response). **Retrieve agent version history:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Show me all published versions of the "support-bot" agent ``` The assistant uses `get_agent` to retrieve the agent including its version history. *** **Check the current configuration of a specific version:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} What are the instructions on version 3.0.0 of the "support-bot" agent? ``` The assistant uses `get_agent` with the version-pinned key `support-bot@3.0.0`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq responses create \ --model agent/my-agent@4.0.0 \ --input '"Hello"' ``` Route by environment with `@environment-name`: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq responses create \ --model agent/my-agent@production \ --input '"..."' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq responses create --help` for the full flag reference. ### Comparing Changes Click on any version to open the **Compare Changes** view. Use the **From** and **To** dropdowns to compare any two versions, including **Current** (unpublished working changes). Side-by-side diff view comparing two agent versions, highlighting changed lines in the instructions field. Two view modes are available: * **Instructions**: diff of agent instructions only. * **Snapshot**: full JSON diff of the complete agent configuration. Use the button to toggle between side-by-side and unified diff layouts. ### Restoring a Version Open **Compare Changes** (see above), then click Restore next to an older version to load it into the current working draft. Restore does not publish automatically: the agent is loaded into the draft as unpublished changes, and Publish still needs to be clicked for it to become a real version. Earlier versions are never deleted, so restoring is always reversible. Restore replaces the full agent configuration, including connected **Knowledge Bases**, **Memory Stores**, [**Team of Agents**](/docs/ai-studio/ai-engineering/run-agents#multi-agent-workflows), **Skills**, **Variables**, and **Tools**. If there are unpublished changes already, a confirmation dialog asks for confirmation before overwriting them. ### Assigning Environments Click on a version to assign it to an environment: * Select an existing environment (e.g. `develop`, `production`). * Create environment: add a new environment from this dropdown. * Manage environments: open the full environment management settings. A version can be assigned to multiple environments. Assigned badges appear on the version row. Version row showing environment badge labels assigned to a specific version. To learn more about environments, see [Environments](/docs/ai-studio/organization/environments). ## Test in the Chat Panel The Chat Panel, labeled **Chat Playground**, sends messages to the agent using its current working configuration, including unpublished changes, so instructions, tools, and model changes can be tested before publishing. * Send a message to see the agent's response, including any tool calls it makes. * Click Chat History to switch between, rename, or delete previous test conversations. Use the Variables button above the message input to test declared variables without calling the API. The button shows a count once the instructions declare one or more variables; click a variable to set its value, using Open in panel for longer values. Sent values persist with the conversation until changed. Chat Panel message input with a Variables button showing a count of 2 declared variables. The Variables panel lists variables already declared with `{{variableName}}` in the instructions. Adding a new variable name is not supported from this panel; add the placeholder to the instructions first. # Chunking Source: https://docs.orq.ai/docs/ai-studio/ai-engineering/chunking Split text into chunks for RAG ingestion with seven strategies, and decide between the Chunking API and Knowledge Base-managed chunking. ## Use cases Split large text into consistent chunks so a retrieval pipeline can index and search it. Fix chunk size and overlap before ingestion instead of relying on defaults. Prepare text for a third-party vector database or an existing embedding pipeline. Inspect, edit, or delete chunks in a Knowledge Base Datasource. ## Overview Chunking splits large documents into smaller pieces that a retrieval pipeline can index and search. **Orq.ai** offers the standalone [Chunking API](/reference/chunking/parse-text) and chunk management inside [Knowledge Bases](/docs/ai-studio/ai-engineering/knowledge-bases). Text becomes chunks through three paths: a Knowledge Base chunks uploaded files automatically, the Chunking API prepares text that is added to a Datasource manually, or the Chunking API feeds an external vector database. The sections below compare the strategies and the ingestion paths. ## Quick start Split text with the `token` strategy to see the API shape. ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/chunking \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Your long text content here...", "strategy": "token", "chunk_size": 512, "chunk_overlap": 0 }' ``` ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env.ORQ_API_KEY ?? "" }); const result = await orq.chunking.parse({ text: "Your long text content here...", strategy: "token", chunkSize: 512, chunkOverlap: 0, }); console.log(result.chunks); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ.get("ORQ_API_KEY")) result = orq.chunking.parse( request={ "text": "Your long text content here...", "strategy": "token", "chunk_size": 512, "chunk_overlap": 0, } ) for chunk in result.chunks: print(f"Chunk {chunk.index}: {chunk.text[:50]}...") ``` ## Which chunking strategy to use The Chunking API offers seven strategies: | Strategy | Best for | Trade-off | | ----------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `token` | Chunks that fit LLM context windows; consistent chunk sizes for embedding models | Boundaries are token-based, not semantic | | `sentence` | Prose where sentence boundaries should be preserved | Chunk size varies with sentence length | | `recursive` | General-purpose splitting that preserves document structure | Splits in a hierarchy of passes over the text | | `semantic` | Topically coherent chunks using embedding similarity | Requires an `embedding_model`; slower and adds embedding cost | | `late` | Chunks embedded with the surrounding document in context | Boundaries match `recursive`; requires an `embedding_model` and adds embedding cost | | `agentic` | Complex documents needing LLM-determined split points | Runs an LLM per call; higher cost and latency | | `fast` | Large files (over 1 MB) where speed and memory matter | Byte-level boundaries, not semantic ones | For the full parameter tables and defaults for each strategy, see [Datasource and Chunking](/docs/ai-studio/ai-engineering/knowledge-bases#datasource-and-chunking). ## Chunk size and overlap * **Chunk size** controls how much context each retrieved unit carries. Smaller chunks retrieve more precisely but produce more chunks, more tokens, and higher embedding and storage cost. Larger chunks give the model more surrounding context but increase token use and generation cost. * **Chunk overlap** repeats tokens across neighboring chunks so a boundary does not cut off a relevant passage. Higher overlap increases redundancy between chunks but improves the likelihood that relevant information is returned to models. Strategy defaults: * `chunk_size` 512 for token, sentence, recursive, and late * `chunk_overlap` 0 for token and sentence (recursive has no overlap parameter) * `agentic` uses `chunk_size` 1024 * `fast` uses `target_size` 4096 bytes See the [strategy tables](/docs/ai-studio/ai-engineering/knowledge-bases#datasource-and-chunking) for the complete list. ## Standalone chunking vs Knowledge Base chunking | Path | How it works | Use when | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | Knowledge Base file upload | Upload a file or create a Datasource with a `file_id`; the Knowledge Base parses, chunks, embeds, and indexes automatically | Fully managed RAG with no custom logic | | Chunking API + Datasource | Call `/v2/chunking`, then add the returned chunks to an empty Datasource; the Knowledge Base embeds and indexes them | Custom strategy, size, or overlap; chunking inside an existing pipeline | | Chunking API + external vector database | Call `/v2/chunking`, then embed and store the chunks in a vector database the application controls | Data must stay in existing infrastructure; custom embedding pipeline | Guides per path: * **Managed upload**: [Create a Datasource](/docs/ai-studio/ai-engineering/knowledge-bases#create-a-datasource) * **Manual pattern**: [Simple RAG cookbook](/docs/ai-studio/cookbooks/common-architecture/simple-rag) * **External storage**: [Use Pinecone and custom vector databases](/docs/ai-studio/cookbooks/integrations-tooling/using-thirdparty-vectordbs-with-orq) ## Inspecting and correcting chunks After ingestion, chunks in a Knowledge Base Datasource can be listed, counted, retrieved, updated, and deleted, one at a time or in bulk. The [Knowledge Bases page](/docs/ai-studio/ai-engineering/knowledge-bases) covers the UI flow and chunk metadata; the reference pages list the exact payloads. | Operation | Endpoint | Reference | | ---------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | Create chunks | `POST /v2/knowledge/{knowledge_id}/datasources/{datasource_id}/chunks` (max 100 per request) | [Create chunks](/reference/knowledge-bases/create-chunks-for-a-datasource) | | List chunks | `GET` the collection, or `POST .../chunks/list` | [List all chunks](/reference/knowledge-bases/list-all-chunks-for-a-datasource) | | Count chunks | `POST .../chunks/count` | [Get chunks total count](/reference/knowledge-bases/get-chunks-total-count) | | Retrieve a chunk | `GET .../chunks/{chunk_id}` | [Retrieve a chunk](/reference/knowledge-bases/retrieve-a-chunk) | | Update a chunk | `PATCH .../chunks/{chunk_id}` | [Update a chunk](/reference/knowledge-bases/update-a-chunk) | | Delete a chunk | `DELETE .../chunks/{chunk_id}` | [Delete a chunk](/reference/knowledge-bases/delete-a-chunk) | | Delete multiple chunks | `DELETE` the collection with `chunk_ids` (max 100) | [Delete multiple chunks](/reference/knowledge-bases/delete-multiple-chunks) | ## Worked example For a complete example that chunks a document with the Chunking API, adds the chunks to a Knowledge Base Datasource, and retrieves them, see [Simple RAG](/docs/ai-studio/cookbooks/common-architecture/simple-rag). For chunking into a custom vector database, see [Use Pinecone and custom vector databases](/docs/ai-studio/cookbooks/integrations-tooling/using-thirdparty-vectordbs-with-orq). ## Best practices * **Clean text before chunking (Knowledge Base path)**: pass `chunking_cleanup_options` inside `chunking_options` when creating a Datasource to remove emails, credit card numbers, and phone numbers, and to normalize whitespace, before content is indexed. See [Datasource and Chunking](/docs/ai-studio/ai-engineering/knowledge-bases#datasource-and-chunking). * **Keep chunk metadata primitive and consistent**: strings, numbers, or booleans only; non-coercible values are discarded from the chunk. * **Match chunk size to the retrieval question**: smaller chunks for precise Q\&A, larger chunks when the model needs broader context. # Create Tools Source: https://docs.orq.ai/docs/ai-studio/ai-engineering/create-tools Add function calling to LLM applications with tools. Create HTTP, Python, or JSON Schema tools to integrate AI models with external APIs and services. Tools give models the ability to take action: call an API, run code, or invoke any external service. Tools require a model with function calling support, look for the `tools` tag in the [AI Gateway](/docs/ai-gateway/using-the-router). For MCP server connections, see [MCP Portal](/docs/ai-gateway/mcp-portal/mcp-servers). The following Tools are available: Pass the tool call back to the caller for local execution. Define parameters with JSON Schema. Enforce structured output from the model using a full JSON Schema definition. Make a real HTTP request to an external API at runtime. No extra code needed. Connect upstream MCP servers. Manage authentication, tool discovery, and exposure from the **AI Gateway**. Run arbitrary Python code at runtime. Define logic and parameters directly in the Studio. ## Choosing a tool type **Orq.ai** executes every tool type except Function: the model calls the tool, the platform runs it, and the run continues without the application being involved. A Function Tool is the one that gets handed back. The platform returns the call and waits for the application to execute it and send the result, which makes it the escape hatch for anything the platform cannot reach on its own. Pick a Function Tool when the code needs the application's own environment: a database connection, an internal client library, credentials that never leave the application, or logic that no single request can express. An [HTTP Tool](#http-tool) is less work when the capability is already a REST endpoint, because **Orq.ai** makes the request. Small self-contained logic can go in a [Python Tool](#python-tool), which runs on the platform with no application code at all. ## Function Tool A Function Tool lets the model call custom code that runs in the application invoking the agent, not on the **Orq.ai** platform. Use it for database queries, internal APIs, or any logic that requires access to the application's environment. Define a callable function using JSON Schema. Open **Tools** in the **Managed Agents** section, then click Tool. Tool Add Enter the main details of the tool: * **Key**, used by models to reference the tool * **Name**, used in the studio to find the tool * **Description**, used to describe the tool Make the Description as precise as possible, it is used notably by [Agents](/docs/ai-studio/ai-engineering/build-agents) when looking up relevant tools for their tasks. Function Tool Configure Function Tools are defined using JSON. Here is an example of a JSON schema for a function `get_current_weather` that declares the fields `location (string)` and `unit (string)`: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "object", "properties": { "unit": { "type": "string", "description": "The temperature unit, e.g. Celsius" }, "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA" } }, "required": [ "location", "unit" ] } ``` The object defined here is based on [JSON Schema](https://json-schema.org/). This framework allows for extensible definition that fits your ideal function definition. **Type** Use here any of the valid JSON types: `object`, `string`, `integer`, `number`, `array`, etc. The top-level type will most commonly be an `object` holding other properties. Learn more about all JSON types in [the JSON Schema definition](https://json-schema.org/understanding-json-schema/reference/type). **Properties** Properties are definitions of fields within an object. Here you can define any new variable. Nested properties are allowed. **Required** The `required` array within an object defines which fields must be entered for a JSON payload to be validated. Once your tool is configured, click Publish to save a new version. Each published version is immutable and tracked in the [version history](#versions). A Function Tool defines a callable function using a JSON Schema parameter definition. The caller executes the function locally and returns the result to the model. | Field | Required | Description | | --------------------- | -------- | ------------------------------------------------------ | | `key` | Yes | Unique identifier (alphanumeric, hyphens, underscores) | | `path` | Yes | Project path, e.g. `"Default"` | | `type` | Yes | Must be `"function"` | | `description` | Yes | Used by agents to decide when and how to call the tool | | `display_name` | No | Human-readable name shown in the Studio | | `function.name` | Yes | Function name | | `function.parameters` | Yes | JSON Schema object describing the function parameters | ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/tools \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "get_weather", "display_name": "Get Weather", "description": "Returns the current weather for a given city", "path": "Default", "type": "function", "function": { "name": "get_weather", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city name, e.g. Amsterdam" }, "unit": { "type": "string", "description": "Temperature unit: celsius or fahrenheit" } }, "required": ["location"] } } }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ["ORQ_API_KEY"]) tool = orq.tools.create(request={ "key": "get_weather", "display_name": "Get Weather", "description": "Returns the current weather for a given city", "path": "Default", "type": "function", "function": { "name": "get_weather", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city name, e.g. Amsterdam", }, "unit": { "type": "string", "description": "Temperature unit: celsius or fahrenheit", }, }, "required": ["location"], }, }, }) ``` ```typescript Node theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env["ORQ_API_KEY"] ?? "" }); const tool = await orq.tools.create({ key: "get_weather", displayName: "Get Weather", description: "Returns the current weather for a given city", path: "Default", type: "function", function: { name: "get_weather", parameters: { type: "object", properties: { location: { type: "string", description: "The city name, e.g. Amsterdam", }, unit: { type: "string", description: "Temperature unit: celsius or fahrenheit", }, }, required: ["location"], }, }, }); ``` See the full [Create Tool API reference](/reference/tools/create-tool). ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} orq tools create \ --key get_weather \ --display-name "Get Weather" \ --description "Returns the current weather for a given city" \ --path Default \ --type function \ --function '{"name": "get_weather", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "The city name, e.g. Amsterdam"}, "unit": {"type": "string", "description": "Temperature unit: celsius or fahrenheit"}}, "required": ["location"]}}' ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq tools create --help` for the full flag reference. ## JSON Schema Tool Enforce structured output from the model using a full JSON Schema definition. Open **Tools** in the **Managed Agents** section, then click Tool. Tool Add Enter the main details of the tool: * **Key**, used by models to reference the tool * **Name**, used in the studio to find the tool * **Description**, used to describe the tool Make the Description as precise as possible, it is used notably by [Agents](/docs/ai-studio/ai-engineering/build-agents) when looking up relevant tools for their tasks. JSON Schema Tool configure JSON Schema Tools are defined using JSON. Here is an example of a JSON schema for a function `get_current_weather` that declares the fields `location (string)` and `unit (string)`: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "type": "object", "properties": { "unit": { "type": "string", "description": "The temperature unit, e.g. Celsius" }, "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA" } }, "required": [ "location", "unit" ] } ``` The object defined here is based on [JSON Schema](https://json-schema.org/). This framework allows for extensible definition that fits your ideal function definition. **Type** Use here any of the valid JSON types: `object`, `string`, `integer`, `number`, `array`, etc. The top-level type will most commonly be an `object` holding other properties. Learn more about all JSON types in [the JSON Schema definition](https://json-schema.org/understanding-json-schema/reference/type). **Properties** Properties are definitions of fields within an object. Here you can define any new variable. Nested properties are allowed. **Required** The `required` array within an object defines which fields must be entered for a JSON payload to be validated. Once your tool is configured, click Publish to save a new version. Each published version is immutable and tracked in the [version history](#versions). A JSON Schema Tool enforces structured output from the model using a full JSON Schema definition. Unlike Function Tools, the schema is defined at the top level with a `name` and `description`. | Field | Required | Description | | ------------------------- | -------- | ------------------------------------------------------ | | `key` | Yes | Unique identifier (alphanumeric, hyphens, underscores) | | `path` | Yes | Project path, e.g. `"Default"` | | `type` | Yes | Must be `"json_schema"` | | `description` | Yes | Used by agents to decide when and how to call the tool | | `display_name` | No | Human-readable name shown in the Studio | | `json_schema.name` | Yes | Schema name | | `json_schema.description` | No | Describes the schema's purpose | | `json_schema.schema` | Yes | JSON Schema object enforcing the output structure | ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/tools \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "extract_contact", "display_name": "Extract Contact", "description": "Extracts contact information from unstructured text", "path": "Default", "type": "json_schema", "json_schema": { "name": "extract_contact", "description": "Extracts name, email, and phone from text", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string" }, "phone": { "type": "string" } }, "required": ["name"] } } }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ["ORQ_API_KEY"]) tool = orq.tools.create(request={ "key": "extract_contact", "display_name": "Extract Contact", "description": "Extracts contact information from unstructured text", "path": "Default", "type": "json_schema", "json_schema": { "name": "extract_contact", "description": "Extracts name, email, and phone from text", "schema": { "type": "object", "properties": { "name": {"type": "string"}, "email": {"type": "string"}, "phone": {"type": "string"}, }, "required": ["name"], }, }, }) ``` ```typescript Node theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env["ORQ_API_KEY"] ?? "" }); const tool = await orq.tools.create({ key: "extract_contact", displayName: "Extract Contact", description: "Extracts contact information from unstructured text", path: "Default", type: "json_schema", jsonSchema: { name: "extract_contact", description: "Extracts name, email, and phone from text", schema: { type: "object", properties: { name: { type: "string" }, email: { type: "string" }, phone: { type: "string" }, }, required: ["name"], }, }, }); ``` See the full [Create Tool API reference](/reference/tools/create-tool). Pipe the body via `--stdin` for tool types other than `function`; per-field flags currently validate `--type` against `function` only. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} echo '{ "key": "extract_contact", "display_name": "Extract Contact", "description": "Extracts contact information from unstructured text", "path": "Default", "type": "json_schema", "json_schema": { "name": "extract_contact", "description": "Extracts name, email, and phone from text", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string" }, "phone": { "type": "string" } }, "required": ["name"] } } }' | orq tools create --stdin ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq tools create --help` for the full flag reference. ## HTTP Tool Make a real HTTP request to an external API at runtime. Use `{{variable}}` syntax to inject dynamic values into any field. Open **Tools** in the **Managed Agents** section, then click Tool. Tool Add Enter the main details of the tool: * **Key**, used by models to reference the tool * **Name**, used in the studio to find the tool * **Description**, used to describe the tool Make the Description as precise as possible, it is used notably by [Agents](/docs/ai-studio/ai-engineering/build-agents) when looking up relevant tools for their tasks. Http Tool Add Pn HTTP Tools are defined within the Studio, either using the UI or using JSON (use the toggle to change mode). Http Tool Configuration Pn | Field | Description | | ------------------ | -------------------------------------------------------------------------------------------------- | | **URL** | Enter the API URL as well as the HTTP Method for the call | | **Header** | Define Request Header Key-value pairs | | **Payload** | Define Request Body Payload Key-value pairs (these are translated to JSON at runtime) | | **Authentication** | Define an optional Bearer Authentication field and Token (Tokens are encrypted when saved in Orq). | You can use **Variables** with the `{{variable}}` syntax within any configuration field. The variable will be resolved at runtime when the payload is built for the HTTP call. Use the `Autogenerate Schema` when using variables to ensure variable definition is correctly created. Once your tool is configured, click Publish to save a new version. Each published version is immutable and tracked in the [version history](#versions). An HTTP Tool makes a real HTTP request to an external API at runtime. Use `{{variable}}` syntax in any field to inject dynamic values. | Field | Required | Description | | -------------------------- | -------- | ------------------------------------------------------ | | `key` | Yes | Unique identifier (alphanumeric, hyphens, underscores) | | `path` | Yes | Project path, e.g. `"Default"` | | `type` | Yes | Must be `"http"` | | `description` | Yes | Used by agents to decide when and how to call the tool | | `display_name` | No | Human-readable name shown in the Studio | | `http.blueprint.url` | Yes | Target URL. Supports `{{variable}}` syntax | | `http.blueprint.method` | Yes | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` | | `http.blueprint.headers` | No | Key-value pairs sent with every request | | `http.blueprint.timeout` | No | Request timeout in seconds, 1 to 600. Defaults to 60 | | `http.blueprint.arguments` | No | Parameters the model can fill in at call time | ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/tools \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "search_products", "display_name": "Search Products", "description": "Searches the product catalog by keyword", "path": "Default", "type": "http", "http": { "blueprint": { "url": "https://api.example.com/products/search", "method": "GET", "headers": { "Accept": "application/json" }, "timeout": 30 }, "arguments": { "query": { "type": "string", "description": "Search keyword", "send_to_model": true } } } }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ["ORQ_API_KEY"]) tool = orq.tools.create(request={ "key": "search_products", "display_name": "Search Products", "description": "Searches the product catalog by keyword", "path": "Default", "type": "http", "http": { "blueprint": { "url": "https://api.example.com/products/search", "method": "GET", "headers": {"Accept": "application/json"}, "timeout": 30, }, "arguments": { "query": { "type": "string", "description": "Search keyword", "send_to_model": True, } }, }, }) ``` ```typescript Node theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env["ORQ_API_KEY"] ?? "" }); const tool = await orq.tools.create({ key: "search_products", displayName: "Search Products", description: "Searches the product catalog by keyword", path: "Default", type: "http", http: { blueprint: { url: "https://api.example.com/products/search", method: "GET", headers: { Accept: "application/json" }, timeout: 30, }, arguments: { query: { type: "string", description: "Search keyword", sendToModel: true, }, }, }, }); ``` See the full [Create Tool API reference](/reference/tools/create-tool). Pipe the body via `--stdin` for tool types other than `function`; per-field flags currently validate `--type` against `function` only. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} echo '{ "key": "search_products", "display_name": "Search Products", "description": "Searches the product catalog by keyword", "path": "Default", "type": "http", "http": { "blueprint": { "url": "https://api.example.com/products/search", "method": "GET", "headers": { "Accept": "application/json" }, "arguments": { "query": { "type": "string", "description": "Search keyword", "send_to_model": true } } } } }' | orq tools create --stdin ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq tools create --help` for the full flag reference. ## MCP Servers MCP servers are now managed centrally in the **AI Gateway** under **MCP Portal**. Each server registers an upstream MCP endpoint, discovers tools automatically, and exposes them to **Agents** and **Gateways**. MCP tools previously created through this page have been migrated to **MCP Portal**. Existing connections are now managed under **AI Gateway > MCP Portal > MCP Servers**. New MCP connections should be created through the **MCP Portal** instead. Connect upstream MCP servers, configure authentication, and manage tool discovery. Bundle multiple servers behind a single gateway endpoint with egress and rate limits. ## Python Tool Python code is limited to 1 MB (1,048,576 bytes) per tool: roughly 1 million characters, or about 20,000 lines of typical Python. Larger code returns a `Code exceeds maximum size` error and does not run. Run arbitrary Python code at runtime. Access parameters via `params` and store the result in `result`. Open **Tools** in the **Managed Agents** section, then click Tool. Tool Add Enter the main details of the tool: * **Key**, used by models to reference the tool * **Name**, used in the studio to find the tool * **Description**, used to describe the tool Make the Description as precise as possible, it is used notably by [Agents](/docs/ai-studio/ai-engineering/build-agents) when looking up relevant tools for their tasks. Create Python Tool Freely define the code to be run during Tool execution. You can define the JSON Schema for the parameters to be sent into the tool. Here, see the `name` field defined and then further fetched using `params.get('name')`. Ensure your return value is stored within the `result` field. Python Tool Config Once your tool is configured, click Publish to save a new version. Each published version is immutable and tracked in the [version history](#versions). A Python Tool runs Python code at runtime. Define the logic directly in the `code` field and declare expected parameters using a JSON Schema. | Field | Required | Description | | ---------------------- | -------- | ------------------------------------------------------------------------------------ | | `key` | Yes | Unique identifier (alphanumeric, hyphens, underscores) | | `path` | Yes | Project path, e.g. `"Default"` | | `type` | Yes | Must be `"code"` | | `description` | Yes | Used by agents to decide when and how to call the tool | | `display_name` | No | Human-readable name shown in the Studio | | `code_tool.language` | Yes | Must be `"python"` | | `code_tool.code` | Yes | Python code to execute. Access parameters via `params`, store the result in `result` | | `code_tool.parameters` | Yes | JSON Schema object describing the expected input parameters | ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST https://my.orq.ai/v2/tools \ -H "Authorization: Bearer $ORQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "key": "calculate_discount", "display_name": "Calculate Discount", "description": "Calculates the discounted price given a price and a discount percentage", "path": "Default", "type": "code", "code_tool": { "language": "python", "code": "price = float(params.get(\"price\", 0))\ndiscount = float(params.get(\"discount\", 0))\nresult = price * (1 - discount / 100)", "parameters": { "type": "object", "properties": { "price": { "type": "number", "description": "Original price" }, "discount": { "type": "number", "description": "Discount percentage (0-100)" } }, "required": ["price", "discount"] } } }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} from orq_ai_sdk import Orq import os orq = Orq(api_key=os.environ["ORQ_API_KEY"]) tool = orq.tools.create(request={ "key": "calculate_discount", "display_name": "Calculate Discount", "description": "Calculates the discounted price given a price and a discount percentage", "path": "Default", "type": "code", "code_tool": { "language": "python", "code": ( 'price = float(params.get("price", 0))\n' 'discount = float(params.get("discount", 0))\n' "result = price * (1 - discount / 100)" ), "parameters": { "type": "object", "properties": { "price": {"type": "number", "description": "Original price"}, "discount": {"type": "number", "description": "Discount percentage (0-100)"}, }, "required": ["price", "discount"], }, }, }) ``` ```typescript Node theme={"theme":{"light":"github-light","dark":"github-dark"}} import { Orq } from "@orq-ai/node"; const orq = new Orq({ apiKey: process.env["ORQ_API_KEY"] ?? "" }); const tool = await orq.tools.create({ key: "calculate_discount", displayName: "Calculate Discount", description: "Calculates the discounted price given a price and a discount percentage", path: "Default", type: "code", codeTool: { language: "python", code: [ 'price = float(params.get("price", 0))', 'discount = float(params.get("discount", 0))', "result = price * (1 - discount / 100)", ].join("\n"), parameters: { type: "object", properties: { price: { type: "number", description: "Original price" }, discount: { type: "number", description: "Discount percentage (0-100)" }, }, required: ["price", "discount"], }, }, }); ``` See the full [Create Tool API reference](/reference/tools/create-tool). Pipe the body via `--stdin` for tool types other than `function`; per-field flags currently validate `--type` against `function` only. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} echo '{ "key": "calculate_discount", "display_name": "Calculate Discount", "description": "Calculates the discounted price given a price and a discount percentage", "path": "Default", "type": "code", "code_tool": { "language": "python", "code": "price = float(params.get(\"price\", 0))\ndiscount = float(params.get(\"discount\", 0))\nresult = price * (1 - discount / 100)", "parameters": { "type": "object", "properties": { "price": { "type": "number", "description": "Original price" }, "discount": { "type": "number", "description": "Discount percentage (0-100)" } }, "required": ["price", "discount"] } } }' | orq tools create --stdin ``` See [install and setup](/reference/cli) to get started with the CLI. Run `orq tools create --help` for the full flag reference. ## Versions When you are done editing, click Publish to save your changes. You will be prompted to write a commit message and choose a version bump: **major**, **minor**, or **patch**. Tool version publish * **Patch** (e.g. `v1.0.0` to `v1.0.1`): small fixes, no behavior change * **Minor** (e.g. `v1.0.0` to `v1.1.0`): new functionality, backwards compatible * **Major** (e.g. `v1.0.0` to `v2.0.0`): breaking change or significant rework Every time you publish, a new version of the tool is created. The **Versions** tab shows the full history. Versions are numbered (e.g. `v1.0.0`, `v1.1.0`) and each entry shows the author and publish timestamp. Tool versions Use the Compare button to open a diff view to see what changed between versions. ## Using Tools All tool types are supported. Reference a tool by `key` in the `settings.tools` array. Your agent's instructions must explicitly describe each tool and when to use it. Learn more about [using tools in Agents](/docs/ai-studio/ai-engineering/build-agents#add-tools). Only **Function Tools** are supported. Import a previously created tool from the **Tools** tab in the deployment configuration. Learn more about [using tools in Deployments](/docs/ai-studio/ai-engineering/deployments#tools). # Create a Deployment Source: https://docs.orq.ai/docs/ai-studio/ai-engineering/deployments Create Orq.ai Deployments to ship LLM use cases to production. Configure model routing, invoke them via API or SDK, and monitor calls in real time. **Deployments** ship Gen AI use cases to production with **Orq.ai** as an AI Gateway. All calls route through the platform, providing routing, monitoring, and security in one place. Connect with a single line of code, iterate without a code release, and benefit from full observability throughout. Common use cases include customer support bots, RAG-powered document Q\&A, content generation pipelines, and any LLM feature that needs reliable model routing, versioning, and production monitoring. Set up a Deployment with a key, model, and system prompt in AI Studio or via MCP. Set the model, fallbacks, variables, knowledge base, tools, caching, and guardrails per Variant. Route traffic across Variants by environment, context attributes, or percentage split. Deploy and roll back configurations without a code release. Call a Deployment via API or SDK and pass identity, usage tracking, and extra parameters. Monitor requests, filter logs by Variant, and inspect full request details. ## Create a Deployment Open **Deployments** in the **Managed Agents** section, then click Deployment. Select **Deployment** from the entity picker. Create Deployment dialog with fields for Deployment Key set to key123 and Model set to claude-sonnet-5. Set the deployment key (alphanumeric) and select the primary model for the first Variant. The Variant editor opens. Use the [Orq MCP server](/docs/ai-studio/integrations/code-assistants/orq-mcp) to manage deployments directly from an AI code assistant. **Find an existing deployment:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Search for the "support-bot" deployment in my workspace ``` The assistant uses `search_entities` with `type: "deployment"` to locate deployments by name or key. *** **Retrieve deployment configuration:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Get the full configuration of the "support-bot" deployment ``` The assistant uses `get_deployment` to return the key, description, model, messages, and variant settings. *** **Create a deployment:** ```prompt wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} Create a customer support deployment called "support-bot" in the Default/Deployments project. Use GPT-5.6 Sol with a professional, concise system prompt. ``` The assistant uses `create_deployment` with the specified `key`, `path`, and `variant` (model and messages). Use `list_models` first to find valid model IDs. ## Configure a Variant **Variants** are different prompt and model configurations available behind one Deployment. A Deployment can hold any number of Variants. On creation, the **Variant** screen opens for model and prompt setup. A Variant Prompt is similar to any other prompt. To learn how to configure a Prompt, see [Creating a Prompt](/docs/ai-studio/prompts/prompts). ### Primary Model, Retries, and Fallback The **Primary Model** panel defines the first model queried through this Variant. **Retries** In case of failure, configure how many times a query is retried with this model. Retries are only triggered when a retry count greater than 0 is configured in the Variant settings. When retries are enabled, **Orq.ai** automatically retries the model provider API call if it returns one of the following HTTP status codes: * 408 Request Timeout * 429 Rate Limit Exceeded * 500 Internal Server Error * 502 Bad Gateway * 503 Service Unavailable * 504 Gateway Timeout * 529 Provider Overloaded **Error handling flow:** 1. If an error code above is returned and retries are configured (retry count > 0), **Orq.ai** retries the Primary Model. 2. If all retry attempts fail (or no retries are configured) AND a Fallback Model is configured, **Orq.ai** routes to the Fallback Model. 3. If the Fallback Model also fails, the error is returned to the calling application. **Fallback Model** The Fallback Model is triggered only if the Primary Model fails after all configured retries are exhausted. Fallback Models can have a different configuration from the Primary Model. Primary Model section showing claude-opus-5 with Fallback Models configured to gpt-5.6-sol with reasoning effort, verbosity, and response format settings. Multiple fallback models can be configured in a Deployment. They fall back to one another in order of configuration. Use the **Add extra fallback** button to declare another model. See how fallbacks and retries work together in a production system. Read our cookbook [Customer Support Chat](/docs/ai-studio/cookbooks/chatbots/buildingcustomersupportchatwithaigateway). **API invocation behavior** When invoking a Deployment via the API, response timing depends on the retry and fallback configuration: * **Success on first try**: Response returned immediately. * **Retry scenario**: Response may be delayed by up to `base_latency × (retry_count + 1)` to account for the initial attempt plus all configured retries. * **Fallback invoked**: Additional latency as the Fallback Model processes the request. * **All retries and fallback failed**: Error returned to the calling application. Set appropriate timeouts on API calls to account for retry and fallback latency. ### Structured Outputs Configure **structured outputs** to ensure consistent and reliable responses from a Deployment. Structured outputs specify the exact format the model should follow when generating a response. Two modes are available: * **JSON Mode**: the model automatically returns a valid JSON object for every generation. * **JSON Schema**: define a schema that explicitly describes the fields, types, and structure of the model output. Once defined, a schema can be saved to the directory for reuse across multiple variants or deployments. Primary Model settings with Response Format set to JSON Schema, showing a schema selector dropdown with get_weather and json_p3ft options. ### Variables and Prompt Templating Reference dynamic values in the prompt using double braces: `{{variable_name}}`. Pass a key-value map to the `inputs` field when invoking and **Orq.ai** substitutes each variable before sending the prompt to the model.