Skip to main content

KeyCall

One consistent interface for validating AI-provider API keys, listing and filtering the models available to them, and making normalized calls, so every product stops rebuilding the same model-picker filters and provider wrappers.

Status: early release. Key validation, model listing and filtering, text generation, streaming, tool calling, native web search with normalized citations, structured JSON output, embeddings, image generation, and image, audio, and document input all work and are live-verified against every provider that supports them. The API is settled but may still shift before 1.0.

Docs: USAGE.md for the full API and CLI reference · ARCHITECTURE.md for the layer diagram and component contracts · CHANGELOG.md for version history.

Quick start

from keycall import KeyCall, Message, ModelCategory, TextInput

with KeyCall(provider="openai", api_key=secret) as client:
    discovery = client.list_models(categories={ModelCategory.TEXT_GENERATION})

    result = client.generate_text(
        model=discovery.models[0].id,
        messages=[Message(role="user", content=[TextInput(text="Hello.")])],
    )

print(result.text)
print(result.usage.total_tokens)
print(result.round_trip_duration_ms)
  • Explicit provider, always. KeyCall never guesses which vendor issued a key and never sends a credential to more than the one provider you name.
  • No credential storage. Keys live in memory for the client's lifetime, wrapped in a redacting type that keeps them out of reprs, logs, traces, exceptions, and pickles. Your app decides how to store them.
  • Model filtering built in. Text-generation models by default; embeddings, image, audio, and other categories on request; unknown models never silently enter the default picker.
  • Typed errors. Invalid key, rate limit, provider outage, timeout, and malformed response are distinguishable, never collapsed into "invalid key."
  • Streaming. stream_text() yields typed events (text increments, citations, tool calls, finish) across all four wire protocols, and refuses to call a stream complete without the provider's own terminal signal.
  • Tool calling. Define tools once and KeyCall normalizes all four call/result wire shapes, streamed or not, carrying the provider echo data some models require back verbatim. It never executes a tool.
  • Image generation. generate_image() returns the picture as bytes with the media type the provider produced, on OpenAI and Gemini; the rest refuse before the network.
  • Embeddings. embed() returns one vector per input, in input order, on OpenAI and Gemini; providers without an embeddings endpoint refuse before the network instead of 404ing.
  • Images, audio, and documents. Pass bytes (or a URL where the provider fetches one) beside your text; KeyCall maps each provider's shape and detects the media type from the content. Support varies by provider and by form, so a refusal happens before the network and names who does accept that kind.
  • Web search with citations. web_search=True turns on the provider's native search tool (OpenAI, Anthropic, Gemini; Perplexity always searches) and returns sources normalized to one Citation shape.
  • Structured output. response_schema=<JSON Schema> is enforced provider-side on OpenAI, Anthropic, Gemini, Moonshot, and Perplexity; on providers without enforcement (DeepSeek, unverified custom targets) KeyCall falls back to guaranteed-valid-JSON mode and adds a result warning rather than claiming a guarantee it can't back. result.text is always the JSON string, regardless of which mechanism produced it.
  • Hardened transport. TLS always verified, redirects refused, response sizes capped, SSRF and DNS-rebinding guards on custom endpoints, and generation is never silently retried.

Provider support

Live-verified 2026-08-09. Every release re-runs a model list, a bounded generation, a stream, a full tool round (streamed and not), and an image read against each provider that supports them, and blocks publishing if any of it fails:

Provider Protocol Listing Generation
OpenAI openai verified verified
Anthropic anthropic verified verified
Google Gemini gemini verified verified
DeepSeek openai-compatible verified verified
Perplexity openai-compatible verified verified
Moonshot/Kimi openai-compatible verified verified
Custom endpoint (explicit base_url) openai-compatible fixtures only fixtures only

OpenAI advertises -latest aliases its own account cannot invoke: on 2026-08-10 GET /v1/models listed gpt-5-chat-latest and gpt-5.1-chat-latest, and generating with either returned "Model not found" while gpt-5.2-chat-latest and gpt-5.3-chat-latest worked. This is the same failure as Gemini's retired models, on a provider people assume is tidier, and it is why verify walks the candidates and reports every attempt rather than trusting the first listed model.

Two further provider quirks worth knowing, both handled:

Gemini keeps retired models in its list endpoint with no lifecycle field to pre-filter on, and withdraws them per account ahead of the published shutdown date: on 2026-08-09 the first six text models it advertised to a new key were all refused, gemini-2.5-* with "no longer available to new users" months before its documented shutdown. KeyCall tries the provider's maintained -latest aliases first, so verification lands on a model that works instead of walking a list of withdrawn ones, and the error for a retired model names those aliases. It also meters quota per model and tier, so one model's 429 says nothing about the next. Its supportedGenerationMethods is a transport signal rather than a modality claim: TTS variants advertise generateContent and then refuse a text response, and so do the Interactions-only, computer-use, and music families, so KeyCall lets the identifier outrank it and keeps those out of the default text picker.

Perplexity's GET /v1/models is scoped to the Agent API and returns vendor-prefixed router models (anthropic/..., perplexity/sonar) that the Sonar route rejects. Sonar's own models are not API-discoverable, so KeyCall maintains them in its catalog and uses the list call purely as a credential check. Note the version prefix: the unversioned https://api.perplexity.ai/models returns 404 for every key, valid or not, so anything validating a key against that path rejects good credentials. /v1/models answers 401 for a bad key and 200 for a good one, which is what makes it usable as a check (verified 2026-08-09).

Structured output notes, per provider

  • OpenAI requires additionalProperties: false on every object level of the schema for its strict json_schema mode, or the request 400s. This is an OpenAI requirement, not a KeyCall one — write schemas with it from the start.
  • Anthropic implements structured output by forcing a single synthetic tool call; it cannot be combined with web_search=True in the same request (forcing one tool prevents the model calling a different one), and KeyCall rejects that combination before any network call.
  • Gemini's equivalent combination (web_search=True with response_schema) is not gated — no live-verified evidence either way that Gemini rejects it, so KeyCall passes it through rather than guessing.
  • DeepSeek hard-requires the literal word "json" somewhere in the prompt for its json_object fallback mode, or it 400s. KeyCall detects this and injects a short system instruction automatically when needed, and always says so via a result warning.
  • Moonshot/Kimi reasoning-capable models can spend the entire max_output_tokens budget on a visible reasoning trace and never emit a final answer if the budget is too small. KeyCall detects the resulting empty-content-with-reasoning-trace response and adds a warning rather than returning a silent empty result; give these models a larger budget than you'd expect a short answer to need.

Because of quirks like these, keycall verify --generate walks the filtered models in provider order and prints the outcome of every attempt until one succeeds, so drift stays visible rather than being masked by a silent retry.

Local viewer

keycall view --source ./keys.toml

Opens a token-protected local web app over your loaded targets: a dashboard with live key checks, a sortable model browser with category filters, a playground that both writes text and makes pictures (web search, image input, and tool calling included), and a verify report that walks every key. Keys never leave the server process and never appear in the browser.

Or double-click / run a launcher from a fresh clone — it creates the venv, installs KeyCall, finds your key file, and starts the viewer: launch.command (macOS), launch.sh (Linux/macOS), launch.bat (Windows).

Verifying keys from the command line

keycall verify --source ./keys.toml
keycall verify --source ./keys.toml --generate

--generate also makes one small bounded call per target. Sources can be TXT, JSON, or TOML, an explicit env:VAR_NAME reference, or an interactive prompt. See keycall-test-keys.example.toml for the format and USAGE.md for the full reference. Keys never appear in output, and KeyCall never writes to or deletes your credential file.

Installation

pip install keycall

Development

pip install -e ".[dev]"
pytest

Author

Built by Mo Shehu.

License

AGPL-3.0-or-later. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keycall-0.9.0.tar.gz (183.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keycall-0.9.0-py3-none-any.whl (124.3 kB view details)

Uploaded Python 3

File details

Details for the file keycall-0.9.0.tar.gz.

File metadata

  • Download URL: keycall-0.9.0.tar.gz
  • Upload date:
  • Size: 183.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for keycall-0.9.0.tar.gz
Algorithm Hash digest
SHA256 de2a00bdbc0affff28329c24ea8727933ba5ebe1b0f7e30e44ac3f0070d0d7fa
MD5 b0b126f54e1cf2047b59d1dbfd992558
BLAKE2b-256 e1b88e5b571e604f82e7ca0d5f6beb6d0075686e9827b5a26ea21e9c0a2fb3a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for keycall-0.9.0.tar.gz:

Publisher: release.yml on shehuphd/keycall

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file keycall-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: keycall-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 124.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for keycall-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 88bd5d26b00abe4f60660a8217a90f3659fc46e5381282b22db964b8dce1d7c9
MD5 489914b903f133a11c8f1c2c291c477f
BLAKE2b-256 5662df56b4eeeec22ea5cae238fe68d0e33d258444143bbc1cde82ff15953bb5

See more details on using hashes here.

Provenance

The following attestation bundles were made for keycall-0.9.0-py3-none-any.whl:

Publisher: release.yml on shehuphd/keycall

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page