Skip to content
On this page

Sophy documentation

Build on a governed AI gateway

Use supported OpenAI client methods at https://sophy.in/v1. Each Sophy key is bound to one primary model and carries its policy, limits, and optional knowledge server-side.

Introduction

Sophy is an OpenAI-compatible gateway and operator console. Applications send input in familiar wire formats; Sophy authenticates the key, resolves its centrally managed configuration, calls the bound model through that key’s project-owned Vercel AI Gateway credential, and records project-attributed usage.

Compatibility is intentionally scoped. Chat Completions and Responses cover text, streaming, multimodal input, and function-tool loops. Dedicated routes cover audio transcription, embeddings, image generation, short-lived file uploads, and the primary model bound to a key. The sections below call out differences from the full OpenAI platform.

BASE URLhttps://sophy.in/v1

Supported API surfaces

Supported Sophy API routes
MethodRouteRequired capabilityCompatibility boundary
POST/v1/chat/completionsLanguage generationText, streaming, multimodal input, function tools
POST/v1/responsesLanguage generationStateless; full input required on every call
POST/v1/audio/transcriptionsTranscriptionMultipart audio; JSON with raw or policy-processed text
POST/v1/embeddingsEmbeddingString input; float or base64 vectors
POST/v1/images/generationsImage generationBase64 image output only
POST/v1/filesAny201 response; 4 MiB; cleanup-eligible after 24 hours
GET/v1/modelsAnyReturns only the primary model bound to this key

One key, one primary model

Bind keys around workloads and call a route the primary model’s capabilities support. The client-sent model never switches that model. A transcription key may separately name a language model for key-owned transcript processing.

Authentication

Send the Sophy key as a Bearer token. Keys look like mw_live_… and are issued inside a project in the Sophy console. Sign in with any verified email to create a renameable My Project, or join an existing project through an Admin invitation.

Authorization: Bearer mw_live_…

Missing Bearer authentication returns 401 missing_api_key. A malformed, unknown, revoked, expired, or inactive-project key returns 401 invalid_api_key. Keys are checked against the database on every request, so edits and revocation apply immediately.

Two credentials, separate jobs

A Sophy API key authenticates your application to Sophy. A Project Admin separately connects a Vercel AI Gateway key that pays for and routes every AI operation in that project. Sophy never returns the stored Gateway secret.

Quickstart

Change the base URL and API key, then call one of the supported methods. The placeholder model is required by some OpenAI SDK methods but Sophy uses the model bound to the key.

from openai import OpenAI

client = OpenAI(
    base_url="https://sophy.in/v1",
    api_key="mw_live_…",
)

resp = client.chat.completions.create(
    model="sophy",  # ignored — the key owns the model
    messages=[{"role": "user", "content": "Hello, Sophy!"}],
)
print(resp.choices[0].message.content)

Chat Completions

POSThttps://sophy.in/v1/chat/completions
Chat Completions request fields
FieldTypeNotes
messagesarrayRequired and non-empty. Supports user, assistant, and tool turns plus text, image, and file content. System/developer handling depends on Agent mode.
streambooleanOptional. Emits OpenAI chat.completion.chunk SSE frames.
stream_optionsobjectOptional. { include_usage: true } adds a final usage-only chunk.
toolsarrayOptional function definitions. Sophy returns calls but never executes them. See Tool calling.
tool_choicestring | objectauto, none, required, or one named function.

Key-owned or ignored: model, temperature, top_p, max_tokens, max_completion_tokens, response_format, n, and user. Sophy applies the key’s configured model and generation parameters instead.

Buffered response

{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "created": 1735689600,
  "model": "anthropic/claude-sonnet-4.6",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "Hello! How can I help?" },
    "finish_reason": "stop"
  }],
  "usage": { "prompt_tokens": 9, "completion_tokens": 7, "total_tokens": 16 }
}

Streaming

A stream begins with an assistant-role chunk, emits text and/or tool-call deltas, finishes with a reason chunk, optionally emits usage, and ends with data: [DONE].

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1735689600,"model":"anthropic/claude-sonnet-4.6","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1735689600,"model":"anthropic/claude-sonnet-4.6","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1735689600,"model":"anthropic/claude-sonnet-4.6","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Responses API

POSThttps://sophy.in/v1/responses

Use client.responses.create(...) for a Responses-compatible text, multimodal, streaming, or function-tool workflow. Sophy is stateless: send the full conversation and any prior function calls/results in input on every request.

Responses API request fields
FieldTypeNotes
inputstring | arrayRequired. Must yield at least one usable message or tool item; send the full state for this call.
streambooleanOptional. Emits numbered, typed Responses SSE events.
toolsarrayOptional flat function-tool definitions. Non-function Responses tool types are ignored.
tool_choicestring | objectOptional. auto, none, required, or one named function.
instructionsstringUsed only when the key has Agent mode enabled; otherwise dropped.
previous_response_idstringNot supported. Returns 400 stateful_unsupported.
storebooleanIgnored. Sophy does not provide OpenAI-hosted response state and returns store: false.

Key-owned or ignored: model, temperature, top_p, max_output_tokens, and text.format.

from openai import OpenAI

client = OpenAI(base_url="https://sophy.in/v1", api_key="mw_live_…")

resp = client.responses.create(
    model="sophy",
    input="Write a haiku about gateways.",
)
print(resp.output_text)
{
  "id": "resp_…",
  "object": "response",
  "created_at": 1735689600,
  "status": "completed",
  "model": "anthropic/claude-sonnet-4.6",
  "store": false,
  "output": [{
    "id": "msg_…",
    "type": "message",
    "status": "completed",
    "role": "assistant",
    "content": [{ "type": "output_text", "text": "Silent routes converge…", "annotations": [] }]
  }],
  "usage": {
    "input_tokens": 11,
    "input_tokens_details": { "cached_tokens": 0 },
    "output_tokens": 9,
    "output_tokens_details": { "reasoning_tokens": 0 },
    "total_tokens": 20
  }
}

Streaming uses events such as response.created, response.output_text.delta, response.function_call_arguments.delta, and response.completed, each with an increasing sequence_number. There is no [DONE] sentinel.

Multimodal input

Language keys can send public image/file URLs or URLs returned by POST /v1/files, provided the bound model supports the content type. Sophy ownership-checks its own uploads; public external URLs pass through to the model provider.

uploaded_url = "https://…/uploads/<project>/<key>/invoice-….pdf"
uploaded_filename = "invoice.pdf"

resp = client.chat.completions.create(
    model="sophy",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Summarize this document."},
            {
                "type": "file",
                "file": {
                    "file_url": uploaded_url,
                    "filename": uploaded_filename,
                },
            },
        ],
    }],
)

Model support still matters

The model bound to the key must accept the media you send. The model catalog in the console exposes capabilities such as image analysis and file input.

Tool / function calling

Sophy passes function definitions to the bound language model and maps its requested calls back to the selected OpenAI wire format. Sophy never executes a tool. Your application runs it, then sends the result and full conversation state on the next call.

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

first = client.chat.completions.create(
    model="sophy",
    tools=tools,
    messages=[{"role": "user", "content": "Weather in Paris?"}],
)
call = first.choices[0].message.tool_calls[0]

second = client.chat.completions.create(model="sophy", tools=tools, messages=[
    {"role": "user", "content": "Weather in Paris?"},
    first.choices[0].message,
    {"role": "tool", "tool_call_id": call.id, "content": "18°C, sunny"},
])

Only function tools are supported; other Responses tool types are ignored. Chat uses the nested { type: "function", function: { ... } } shape; Responses uses the flat { type: "function", name, parameters } shape. Legacy Chat functions returns 400 functions_unsupported.

Tools and structured output are mutually exclusive

A key with a JSON Schema rejects tools with 400 tools_unsupported because both features compete for the model’s output channel.

Structured output

An operator can attach a JSON Schema to a language key. Sophy normalizes the schema across supported model providers. Buffered calls use constrained generation, tolerant JSON extraction and retry, then validate the completed object; streaming uses schema-constrained generation with post-hoc validation.

The schema lives on the key. Client-sent Chat response_format and Responses text.format do not replace it. Successful JSON is returned as the assistant text, so parse choices[0].message.content or response.output_text.

Buffered and streaming failure behavior differs

A buffered response that still fails validation returns 502 schema_validation_failed. Streamed bytes cannot be retracted after a 200 begins; Sophy records a validation failure after completion, but the client may already have received non-conforming text.

Audio transcription

POSThttps://sophy.in/v1/audio/transcriptions

Bind the key’s primary model to a transcription model and send audio as multipart/form-data. Sophy returns raw text when no processing policy is set, or applies centrally managed instructions in a second language-model step and returns only that processed text.

Audio transcription request fields
FieldTypeNotes
fileFileRequired, non-empty, and at most 4 MiB (4,194,304 bytes).
modelstringOptional for wire compatibility and ignored. The key’s primary transcription model wins.
languagestringOptional two-letter ISO-639-1 language hint, such as en.
response_formatjsonOptional; defaults to json. Other OpenAI transcription formats are not supported in this version.
advanced transcription optionsunsupportedClient prompt, temperature, logprobs, stream, timestamps, diarization, include, chunking, and known-speaker fields are rejected with 400 unsupported_transcription_option.

Accepted audio formats are MP3/MPEG/MPGA, MP4/M4A, WAV, WebM, FLAC, and OGG. Sophy identifies the format from the file bytes instead of trusting its name or client-declared MIME type. Files outside that allowlist return 400 unsupported_audio_format; files over the limit return 413 file_too_large.

from openai import OpenAI

client = OpenAI(base_url="https://sophy.in/v1", api_key="mw_live_…")

with open("meeting.m4a", "rb") as audio:
    resp = client.audio.transcriptions.create(
        model="sophy",
        file=audio,
        language="en",
        response_format="json",
    )

print(resp.text)
{
  "text": "Decisions: launch on Friday and send the checklist today.",
  "processed": true,
  "language": "en",
  "duration": 8.4
}

Processing policy controls what the client receives

With a blank key system prompt, text and transcript are identical and processed is false. With a non-blank system prompt, the operator must also set params.transcriptProcessorModel to a language model. Sophy transcribes first, then applies the key-owned instructions to produce text. The raw transcript field is omitted so a client cannot bypass centrally managed processing such as redaction. language and duration appear only when the transcription provider returns them.

Compatibility boundary

This route returns JSON only. Client prompts, temperature, logprobs, streaming, timestamp granularities, diarization, chunking, known-speaker hints, subtitle text, and verbose transcription formats are not supported in this version.

One uploaded clip consumes one RPM slot and counts as one client request. Sophy attributes transcription and processing to their actual models, while the processor component does not add a second client request. If content logging is enabled on the key, Sophy stores the filename, byte count, language hint, raw transcript, and final text—but never the audio bytes. Turn content logging off when unprocessed text must not be retained.

Embeddings

POSThttps://sophy.in/v1/embeddings

Bind the key to an embedding model and use the standard OpenAI embeddings method. The key-owned model is returned in the response, and usage records input tokens and estimated cost.

Embeddings request fields
FieldTypeNotes
inputstring | string[]Required. One non-empty string or 1–2,048 non-empty strings.
encoding_formatfloat | base64Optional; defaults to float. Base64 contains little-endian float32 bytes.
dimensionsintegerOptional, 1–100,000, and supported only for openai/* embedding models. Provider limits may be narrower.
modelstringIgnored. The embedding model bound to the key wins.
userstringIgnored.

Pre-tokenized integer arrays are not portable across a gateway-bound model and return 400 token_input_unsupported.

from openai import OpenAI

client = OpenAI(base_url="https://sophy.in/v1", api_key="mw_live_…")

resp = client.embeddings.create(
    model="sophy",
    input=["First document", "Second document"],
    encoding_format="float",
)
print(resp.data[0].embedding)
{
  "object": "list",
  "data": [
    { "object": "embedding", "index": 0, "embedding": [0.012, -0.044, 0.008] },
    { "object": "embedding", "index": 1, "embedding": [-0.031, 0.017, 0.052] }
  ],
  "model": "openai/text-embedding-3-small",
  "usage": { "prompt_tokens": 5, "total_tokens": 5 }
}

Image generation

POSThttps://sophy.in/v1/images/generations

Bind the key to a supported image model. Generated images are returned inline as base64 and are not stored by Sophy.

Image generation request fields
FieldTypeNotes
promptstringRequired and non-empty.
nintegerOptional, from 1 to 10.
sizestringOptional WIDTHxHEIGHT syntax with 2–5 digits per side, such as 1024x1024; provider support varies.
response_formatb64_jsonOptional. b64_json is the only supported format; url is rejected.
quality / stylestringOptional provider-specific options.
background / output_formatstringOptional provider-specific options; unsupported values may be ignored upstream.
model / userstringIgnored. The image model bound to the key wins.
from openai import OpenAI
import base64

client = OpenAI(base_url="https://sophy.in/v1", api_key="mw_live_…")

resp = client.images.generate(
    model="sophy",
    prompt="A red panda coding at a desk, watercolor",
    n=1,
    size="1024x1024",
)
with open("out.png", "wb") as f:
    f.write(base64.b64decode(resp.data[0].b64_json))
{
  "created": 1735689600,
  "data": [{ "b64_json": "iVBORw0KGgoAAAANSUhEUgAA…" }]
}

Files

POSThttps://sophy.in/v1/files

Upload a file as multipart/form-data using the field name file. Success returns 201. Use the returned URL in a Chat file/image part or a Responses input_file/input_image part.

Accepted content types are PDF, PNG, JPEG, WebP, GIF, plain text, CSV, and JSON. The maximum size is 4 MiB (4,194,304 bytes). Uploads become eligible for batched cleanup after 24 hours, so they are request staging—not durable file storage or a precise expiry service.

curl https://sophy.in/v1/files \
  -H "Authorization: Bearer mw_live_…" \
  -F "file=@./invoice.pdf"
{
  "id": "https://…/uploads/<project>/<key>/invoice-….pdf",
  "url": "https://…/uploads/<project>/<key>/invoice-….pdf",
  "pathname": "uploads/<project>/<key>/invoice-….pdf",
  "filename": "invoice.pdf",
  "bytes": 48213,
  "contentType": "application/pdf"
}

Upload URLs are public but unguessable so model providers can fetch them; treat each URL as a bearer URL, not a secret store. When a Sophy URL is submitted back through the API, Sophy checks its issuing key, and cross-key reuse returns 403 file_access_denied.

Models

GEThttps://sophy.in/v1/models

Returns exactly the primary model currently bound to the authenticated key in OpenAI list shape. This route does not return the full console catalog or an optional transcript processor, and it does not select a model for a later request.

{
  "object": "list",
  "data": [
    {
      "id": "anthropic/claude-sonnet-4.6",
      "object": "model",
      "created": 0,
      "owned_by": "sophy"
    }
  ]
}

Key-owned policy

The key is Sophy’s unit of configuration. Operators can change its policy without changing or redeploying the client. The next request reads the latest values.

Configuration stored on a Sophy key
FieldTypeNotes
modelmodel idOne primary model id. Use API routes supported by its catalog capabilities.
system promptstringAuthoritative prompt for language calls and optional transcript processing. Client prompts are dropped or rejected.
transcript processor modellanguage model idOptional params.transcriptProcessorModel. Required when a transcription key has a non-blank system prompt.
generation paramsobjectTemperature, top-p, and max output tokens for language calls, including transcript processing.
output schemaJSON SchemaOptional structured output policy for language calls.
knowledgebasereferenceOptional grounding source for Chat and Responses.
RPM / monthly capnumberOptional request-rate and proxy-traffic cost controls.
content loggingbooleanCaptures buffered Chat, buffered/streaming Responses, embedding inputs, and text from audio transcription. Audio bytes are never stored. Streaming Chat and image prompts record usage metadata only.
owner / statuspolicyConsole scope, active/revoked views, rotation, and immediate revocation.

Client values for model and generation parameters are silently ignored. Chat and Responses prompt behavior is controlled separately by Agent mode. Audio transcription rejects a client prompt; only the key-owned system prompt can request post-processing.

When a language or transcript-processing call has an effective system prompt, Sophy prepends a fixed platform security preamble before the key and any applicable Agent-mode client instructions. Eligible multi-turn Anthropic requests also receive an ephemeral prompt-cache breakpoint; cache reads appear in Responses usage as cached_tokens.

Agent mode

Standard keys are governed mode: Sophy drops client system and developer messages plus Responses instructions, then uses the key-owned prompt.

For a trusted server-side agent whose instructions must vary per request, an operator can enable Agent mode on the key. Sophy keeps the key prompt ahead of client instructions (after Sophy’s fixed security preamble), then appends:

  • Chat: consecutive leading system/developer messages, up to the first non-system turn.
  • Responses: instructions, followed by leading system/developer input items.

Later system-role items in the conversation are still dropped. Model, generation parameters, schema, knowledge, budgets, and rate limits remain key-owned.

Trust boundary

Enable Agent mode only for server-side applications that construct their own message array. Client instructions become operator-level input; never forward end-user-authored leading system/developer content on an Agent-mode key.

Knowledgebases

Project Admins can create project-scoped knowledgebases from PDF, DOCX, Markdown, plain text, CSV, and JSON documents up to 4 MiB each. Sophy extracts, chunks, and embeds sources in the background, then lets keys in that project attach to the same collection.

On Chat or Responses requests with usable user text, Sophy embeds the latest user text (capped at its first 8,000 characters), retrieves the six closest chunks, and adds them to the effective prompt. Image- or file-only turns have no retrieval query. A transient retrieval timeout can continue without grounding. Missing, invalid, or billing-blocked project Gateway credentials fail closed before the model call; they never fall back to Sophy’s deployment credentials or another project.

Knowledgebase ingestion and query-embedding spend is tracked separately from client proxy traffic in the console.

Operator console

One passwordless identity can belong to multiple projects with an independent Admin or Editor role in each. The project in the URL is the tenant boundary, and every read, mutation, key, knowledgebase, log, evaluation, and audit event stays inside it.

Models

Search and compare the catalog by provider, type, capabilities, context window, and estimated pricing.

Projects and Gateway access

Switch projects, choose a personal default, invite members, rename the project, and connect or rotate its encrypted Vercel Gateway credential.

Sophy keys and knowledge

Create, edit, rotate, revoke, bulk-update, assign project-member ownership, and attach project knowledgebases.

Usage and logs

Filter requests, tokens, estimated cost, latency, model mix, errors, and source-tagged spend.

Champion vs challenger evals

Shadow successful live text requests, blind-judge results, and compare quality, cost, latency, and projected impact.

Evaluation behavior and privacy

Evals observe eligible successful text requests only; requests containing media/file content or supplying tools are skipped. While a run is active, replayable sample content is temporarily captured even when normal content logging is off, then purged when the run ends. Sophy recommends a winner but never switches the key automatically.

Errors and retries

Before a response stream starts, Sophy-generated failures use the OpenAI error envelope below with Cache-Control: no-store. Field validation errors may use code: null and identify the field in param.

{
  "error": {
    "message": "Invalid API key.",
    "type": "authentication_error",
    "param": null,
    "code": "invalid_api_key"
  }
}
Named Sophy API error codes
StatusTypeCodeWhen
400invalid_request_errorfunctions_unsupportedLegacy Chat Completions `functions` was sent; use `tools`.
400invalid_request_errorstateful_unsupportedResponses `previous_response_id` was sent; send the full input each call.
400invalid_request_errortools_unsupportedFunction tools were sent to a key configured for structured output.
400invalid_request_errorinvalid_content_typeAudio transcription was not sent as multipart/form-data.
400invalid_request_errorinvalid_multipart_formThe audio transcription multipart body could not be parsed.
400invalid_request_errormissing_fileThe transcription multipart body did not contain a `file` field.
400invalid_request_errorinvalid_fileThe transcription `file` field was not a valid uploaded file.
400invalid_request_errorempty_fileThe uploaded audio file contained no bytes.
400invalid_request_errorunsupported_audio_formatThe uploaded bytes were not recognized as an allowed audio format.
400invalid_request_errorinvalid_languageThe optional transcription `language` value was invalid.
400invalid_request_errorunsupported_transcription_optionA client `prompt`, streaming, or timestamp option was sent.
400invalid_request_errormodel_not_transcriptionThe key’s known model cannot handle buffered audio transcription, including realtime-only models.
400invalid_request_errortranscript_processor_requiredThe key has transcript-processing instructions but no processor model.
400invalid_request_errorprocessor_model_not_languageThe configured transcript processor is a known non-language model.
400invalid_request_errorinvalid_urlA Chat or Responses image/file part contains a malformed URL.
400invalid_request_errormodel_not_imageImage generation was requested with a known non-image key model.
400invalid_request_errorunsupported_response_formatImage output requested anything but `b64_json`, or transcription requested anything but `json`.
400invalid_request_errormodel_not_embeddingEmbeddings were requested with a known non-embedding key model.
400invalid_request_errortoken_input_unsupportedPre-tokenized embedding input was sent; use strings.
400invalid_request_errorunsupported_encoding_formatEmbedding `encoding_format` was not `float` or `base64`.
400invalid_request_errordimensions_unsupported`dimensions` was sent to a non-OpenAI embedding model.
400invalid_request_errorunsupported_file_typeThe uploaded file content type is outside the allowlist.
400invalid_request_errorupstream_invalid_requestThe model provider rejected the request as invalid; its actionable detail is returned.
401authentication_errormissing_api_keyNo valid Bearer Authorization header was supplied.
401authentication_errorinvalid_api_keyThe key is malformed, unknown, revoked, expired, or belongs to an inactive project.
402insufficient_quotaquota_exceededThe key reached its monthly proxy-traffic cost cap.
403invalid_request_errorfile_access_deniedA request references a Sophy upload owned by another key.
413invalid_request_errorfile_too_largeA file or audio upload exceeds 4 MiB (4,194,304 bytes).
429rate_limit_errorrate_limit_exceededThe key RPM limit or an upstream provider rate/quota limit was reached.
502api_errorschema_validation_failedA buffered structured response could not satisfy the key schema.
502api_errorupload_failedSophy could not persist a file upload.
502api_errorupstream_errorA transient upstream network or server failure occurred.
503api_errorproject_gateway_unavailableThe key is valid, but its project has no usable Vercel AI Gateway credential.

Upstream client-input rejections (400/413/422) become 400 upstream_invalid_request with a safe, capped message. Upstream rate limits preserve 429 and forward Retry-After when present. A valid Sophy key whose project credential is missing, disconnected, invalid, billing-blocked, or undecryptable receives 503 project_gateway_unavailable; Sophy makes no fallback upstream call. Transient network and server failures are hidden behind 502 upstream_error.

Errors after streaming starts

Once an SSE response has begun with 200, a later model failure cannot be replaced by a JSON error envelope. Chat still attempts a final [DONE] but may lack its normal finish/usage chunks; Responses may omit response.completed.

Rate limits and monthly quota

Chat, Responses, audio transcription, embeddings, and image generation enforce the key’s optional requests-per-minute limit and monthly cost cap before the paid model call. /files and /models do not consume those request counters.

  • RPM uses a fixed 60-second window. Exceeding it returns 429 rate_limit_exceeded with Retry-After in seconds.
  • Monthly quota uses the current UTC calendar month and counts client-attributable spend, including transcript processing. Reaching it returns 402 quota_exceeded.
  • Evaluation and knowledgebase spend is recorded under separate sources for visibility and does not consume a key’s proxy-traffic cap.

Console cost values are gateway estimates for operational decisions, not a billing-grade ledger.

Need a key or a policy change? Sign in to the Sophy console.