From 1fa1dbc8883907f0b8477700df4228d9fdd8836d Mon Sep 17 00:00:00 2001 From: Romulus Hill Date: Fri, 28 Aug 2026 08:37:36 +0000 Subject: [PATCH] feat: add Scalattice OpenAI-compatible provider --- docs/docs.json | 1 + .../integration-method/scalattice.mdx | 90 +++++++++++++++++++ packages/cost/models/provider-helpers.ts | 5 ++ packages/cost/models/providers/index.ts | 3 + packages/cost/models/providers/priorities.ts | 1 + packages/cost/models/providers/scalattice.ts | 14 +++ packages/cost/providers/mappings.ts | 9 ++ packages/cost/usage/getUsageProcessor.ts | 1 + web/data/providers.ts | 11 +++ .../assets/home/providers/scalattice.svg | 4 + worker/test/setup.ts | 18 ++++ 11 files changed, 157 insertions(+) create mode 100644 docs/getting-started/integration-method/scalattice.mdx create mode 100644 packages/cost/models/providers/scalattice.ts create mode 100644 web/public/assets/home/providers/scalattice.svg diff --git a/docs/docs.json b/docs/docs.json index 2e7b990055..cc010e5443 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -193,6 +193,7 @@ "getting-started/integration-method/mistral", "getting-started/integration-method/nebius", "getting-started/integration-method/novita", + "getting-started/integration-method/scalattice", { "group": "Nvidia", "pages": [ diff --git a/docs/getting-started/integration-method/scalattice.mdx b/docs/getting-started/integration-method/scalattice.mdx new file mode 100644 index 0000000000..0f57178e16 --- /dev/null +++ b/docs/getting-started/integration-method/scalattice.mdx @@ -0,0 +1,90 @@ +--- +title: "Scalattice Integration" +sidebarTitle: "Scalattice" +description: "Connect Helicone with Scalattice, an OpenAI-compatible inference marketplace. Log requests, cost, and latency through a simple base URL change." +"twitter:title": "Scalattice Integration - Helicone OSS LLM Observability" +--- + +import LegacyWarning from "/snippets/legacy-provider-warning.mdx"; + + + +Scalattice is an OpenAI-compatible inference marketplace. Create a key from the [developer docs](https://scalattice.cloud/docs/developers). + +# Gateway Integration + + + + Log into [helicone](https://www.helicone.ai) or create an account. Once you have an account, you + can generate an [API key](https://helicone.ai/developer). + + + Create an `slt_...` key from the [Scalattice developer dashboard](https://scalattice.cloud/developers). + + +```javascript +HELICONE_API_KEY= +SCALATTICE_API_KEY= +``` + + + +Replace `https://api.scalattice.cloud/v1` with `https://scalattice.helicone.ai/v1` and add the Helicone auth header: + +```javascript +Authorization: Bearer +Helicone-Auth: Bearer +``` + + + + +## Example + +```python +from openai import OpenAI +import os + +client = OpenAI( + api_key=os.environ["SCALATTICE_API_KEY"], + base_url="https://scalattice.helicone.ai/v1", + default_headers={ + "Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}", + }, +) + +response = client.chat.completions.create( + model="qwen-3-14b", + messages=[{"role": "user", "content": "Hello from Scalattice"}], +) +print(response.choices[0].message.content) +``` + +```bash +curl https://scalattice.helicone.ai/v1/chat/completions \ + -H "Authorization: Bearer $SCALATTICE_API_KEY" \ + -H "Helicone-Auth: Bearer $HELICONE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "qwen-3-14b", + "messages": [{"role": "user", "content": "Hello from Scalattice"}] + }' +``` + +If the dedicated `scalattice.helicone.ai` host is not live yet, use the generic OpenAI proxy and set the target: + +```python +client = OpenAI( + api_key=os.environ["SCALATTICE_API_KEY"], + base_url="https://oai.helicone.ai/v1", + default_headers={ + "Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}", + "Helicone-Target-URL": "https://api.scalattice.cloud", + }, +) +``` + +Catalog IDs match `GET https://api.scalattice.cloud/v1/models`. + +For more on headers, see [Helicone Headers](https://docs.helicone.ai/helicone-headers/header-directory#utilizing-headers). +For Scalattice itself, see the [developer docs](https://scalattice.cloud/docs/developers). diff --git a/packages/cost/models/provider-helpers.ts b/packages/cost/models/provider-helpers.ts index ae00f70ee3..4f9d9ae44f 100644 --- a/packages/cost/models/provider-helpers.ts +++ b/packages/cost/models/provider-helpers.ts @@ -62,6 +62,8 @@ export function heliconeProviderToModelProviderName( return "fireworks"; case "CANOPYWAVE": return "canopywave"; + case "SCALATTICE": + return "scalattice"; // new registry does not have case "LOCAL": case "HELICONE": @@ -159,6 +161,9 @@ export const dbProviderToProvider = ( if (provider === "nebius" || provider === "Nebius") { return "nebius"; } + if (provider === "scalattice" || provider === "Scalattice") { + return "scalattice"; + } return null; }; diff --git a/packages/cost/models/providers/index.ts b/packages/cost/models/providers/index.ts index 963717d50d..aaf70a9d45 100644 --- a/packages/cost/models/providers/index.ts +++ b/packages/cost/models/providers/index.ts @@ -17,6 +17,7 @@ import { NovitaProvider } from "./novita"; import { OpenAIProvider } from "./openai"; import { OpenRouterProvider } from "./openrouter"; import { PerplexityProvider } from "./perplexity"; +import { ScalatticeProvider } from "./scalattice"; import { VertexProvider } from "./vertex"; import { XAIProvider } from "./xai"; @@ -41,6 +42,7 @@ export const providers = { openai: new OpenAIProvider(), openrouter: new OpenRouterProvider(), perplexity: new PerplexityProvider(), + scalattice: new ScalatticeProvider(), vertex: new VertexProvider(), xai: new XAIProvider() } as const; @@ -84,6 +86,7 @@ export const ResponsesAPIEnabledProviders: ModelProviderName[] = [ "novita", "openrouter", "perplexity", + "scalattice", "xai", "baseten", "fireworks", diff --git a/packages/cost/models/providers/priorities.ts b/packages/cost/models/providers/priorities.ts index 38df67014c..e6e929ef7e 100644 --- a/packages/cost/models/providers/priorities.ts +++ b/packages/cost/models/providers/priorities.ts @@ -34,6 +34,7 @@ export const PROVIDER_PRIORITIES: Record = { mistral: 4, nebius: 4, novita: 4, + scalattice: 4, perplexity: 4, vertex: 4, diff --git a/packages/cost/models/providers/scalattice.ts b/packages/cost/models/providers/scalattice.ts new file mode 100644 index 0000000000..7aeda075e2 --- /dev/null +++ b/packages/cost/models/providers/scalattice.ts @@ -0,0 +1,14 @@ +import { BaseProvider } from "./base"; +import type { Endpoint, RequestParams } from "../types"; + +export class ScalatticeProvider extends BaseProvider { + readonly displayName = "Scalattice"; + readonly baseUrl = "https://api.scalattice.cloud/v1"; + readonly auth = "api-key" as const; + readonly pricingPages = ["https://scalattice.com/pricing/"]; + readonly modelPages = ["https://scalattice.cloud/docs/developers"]; + + buildUrl(endpoint: Endpoint, requestParams: RequestParams): string { + return `${this.baseUrl}/chat/completions`; + } +} diff --git a/packages/cost/providers/mappings.ts b/packages/cost/providers/mappings.ts index 7e856b54c5..f1ab63c67d 100644 --- a/packages/cost/providers/mappings.ts +++ b/packages/cost/providers/mappings.ts @@ -94,6 +94,9 @@ const cerebras = /^https:\/\/api\.cerebras\.ai/; // https://inference.canopywave.io const canopywave = /^https:\/\/inference\.canopywave\.io/; +// https://api.scalattice.cloud +const scalattice = /^https:\/\/api\.scalattice\.cloud/; + export const providersNames = [ "OPENAI", "ANTHROPIC", @@ -132,6 +135,7 @@ export const providersNames = [ "CEREBRAS", "BASETEN", "CANOPYWAVE", + "SCALATTICE", ] as const; export type ProviderName = (typeof providersNames)[number]; @@ -325,6 +329,11 @@ export const providers: { pattern: canopywave, provider: "CANOPYWAVE", costs: [], + }, + { + pattern: scalattice, + provider: "SCALATTICE", + costs: [], } ]; diff --git a/packages/cost/usage/getUsageProcessor.ts b/packages/cost/usage/getUsageProcessor.ts index 3dc612abf9..091c6247ab 100644 --- a/packages/cost/usage/getUsageProcessor.ts +++ b/packages/cost/usage/getUsageProcessor.ts @@ -27,6 +27,7 @@ export function getUsageProcessor( case "fireworks": case "cerebras": case "perplexity": + case "scalattice": return new OpenAIUsageProcessor(); case "anthropic": return new AnthropicUsageProcessor(); diff --git a/web/data/providers.ts b/web/data/providers.ts index 8a22235279..4fb87747f2 100644 --- a/web/data/providers.ts +++ b/web/data/providers.ts @@ -274,6 +274,17 @@ export const providers: Provider[] = [ apiKeyPlaceholder: "...", relevanceScore: 3, }, + { + id: "scalattice", + name: "Scalattice", + logoUrl: "/assets/home/providers/scalattice.svg", + description: "Configure your Scalattice API keys for OpenAI-compatible inference", + docsUrl: + "https://docs.helicone.ai/getting-started/integration-method/scalattice", + apiKeyLabel: "Scalattice API Key", + apiKeyPlaceholder: "slt_...", + relevanceScore: 3, + }, { id: "helicone", name: "Helicone Inference", diff --git a/web/public/assets/home/providers/scalattice.svg b/web/public/assets/home/providers/scalattice.svg new file mode 100644 index 0000000000..959e99b251 --- /dev/null +++ b/web/public/assets/home/providers/scalattice.svg @@ -0,0 +1,4 @@ + +Scalattice + + diff --git a/worker/test/setup.ts b/worker/test/setup.ts index 5c4865510d..f27df93977 100644 --- a/worker/test/setup.ts +++ b/worker/test/setup.ts @@ -282,6 +282,15 @@ vi.mock("@supabase/supabase-js", () => ({ config: null, byok_enabled: isByokEnabled, }, + scalattice: { + org_id: "test-org-id", + provider_name: "scalattice", + decrypted_provider_key: "test-scalattice-api-key", + decrypted_provider_secret_key: null, + auth_type: "api_key", + config: null, + byok_enabled: isByokEnabled, + }, openrouter: { org_id: "test-org-id", provider_name: "openrouter", @@ -431,6 +440,15 @@ vi.mock("@supabase/supabase-js", () => ({ config: null, byok_enabled: true, }, + scalattice: { + org_id: "0afe3a6e-d095-4ec0-bc1e-2af6f57bd2a5", + provider_name: "scalattice", + decrypted_provider_key: "helicone-scalattice-api-key", + decrypted_provider_secret_key: null, + auth_type: "api_key", + config: null, + byok_enabled: true, + }, nebius: { org_id: "0afe3a6e-d095-4ec0-bc1e-2af6f57bd2a5", provider_name: "nebius",