pip install fastspecfastspec turns any OpenAPI (or Google Discovery) spec into a fully async Python client. Load a spec, create a client, and call any endpoint with attribute chaining.
fastspec supports both OpenAPI (JSON/YAML) and Google Discovery specs:
import yamlspecs_path = Path('../specs/')
# OpenAPI specs (Anthropic, OpenAI, GitHub, Stripe)
ant_spec = SpecParser.from_openapi(dict2obj(yaml.safe_load((specs_path/'anthropic.yml').read_text())))
oai_spec = SpecParser.from_openapi(dict2obj(yaml.safe_load((specs_path/'openai.with-code-samples.yml').read_text())))
gh_spec = SpecParser.from_openapi(dict2obj(json.loads((specs_path/'github.json').read_text())))
# Google Discovery spec (Gemini)
gem_spec = SpecParser.from_discovery(dict2obj(json.loads((specs_path/'gemini.json').read_text())))
ant_spec, oai_spec, gh_spec, gem_spec(SpecParser(base_url='https://api.anthropic.com', ops=47),
SpecParser(base_url='https://api.openai.com/v1', ops=241),
SpecParser(base_url='https://api.github.com', ops=1112),
SpecParser(base_url='https://generativelanguage.googleapis.com/', ops=79))
Pass a parsed spec and any required auth headers to OpenAPIClient:
ant_cli = OpenAPIClient(ant_spec, headers={"x-api-key": os.environ["ANTHROPIC_API_KEY"], "anthropic-version": "2023-06-01"})
oai_cli = OpenAPIClient(oai_spec, headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"})
gh_cli = OpenAPIClient(gh_spec, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})Every client organizes endpoints into groups. Use doc() to browse what’s available:
ant_cli.messages- messages.messages_post(model, messages, max_tokens, cache_control, container, inference_geo, metadata, output_config, service_tier, stop_sequences, stream, system, temperature, thinking, tool_choice, tools, top_k, top_p): Create a Message
- messages.message_batches_post(requests): Create a Message Batch
- messages.message_batches_list(before_id, after_id, limit): List Message Batches
- messages.message_batches_retrieve(message_batch_id): Retrieve a Message Batch
- messages.message_batches_delete(message_batch_id): Delete a Message Batch
- messages.message_batches_cancel(message_batch_id): Cancel a Message Batch
- messages.message_batches_results(message_batch_id): Retrieve Message Batch results
- messages.messages_count_tokens_post(messages, model, cache_control, output_config, system, thinking, tool_choice, tools): Count tokens in a Message
- messages.beta_message_batches_post(requests): Create a Message Batch
- messages.beta_message_batches_list(before_id, after_id, limit): List Message Batches
- messages.beta_message_batches_retrieve(message_batch_id): Retrieve a Message Batch
- messages.beta_message_batches_delete(message_batch_id): Delete a Message Batch
- messages.beta_message_batches_cancel(message_batch_id): Cancel a Message Batch
- messages.beta_message_batches_results(message_batch_id): Retrieve Message Batch results
- messages.beta_messages_count_tokens_post(messages, model, cache_control, context_management, mcp_servers, output_config, output_format, speed, system, thinking, tool_choice, tools): Count tokens in a Message
Drill into any operation to see its full signature and parameter docs:
ant_cli.models.models_getGet a Model
Parameters:
- model_id (str, required): Model identifier or alias.
A simple message request:
resp = await ant_cli.messages.messages_post(
model="claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "What is FastSpec?"}],
max_tokens=64,)
resp['content'][0]['text']"FastSpec could refer to a few different things depending on the context. Here are the most likely meanings:\n\n## 1. **Testing Framework**\nFastSpec is a testing framework, particularly associated with Scala development. It's designed to provide:\n- Fast test execution\n- Clear, readable test syntax"
With streaming — just pass stream=True and iterate:
resp = await ant_cli.messages.messages_post(
model="claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "Say hello in 3 languages."}],
max_tokens=128, stream=True)
async for ev in resp:
if ct:= nested_idx(ev,'delta','text'): print(ct, end=' ')Hello! Here are gr eetings in 3 languages:
1. **English**: Hello!
2. **Spanish**: ¡Hola!
3 . **French**: Bonjour!
resp = await oai_cli.chat.create_chat_completion(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "What is fastspec?"}],
max_tokens=64)
resp['choices'][0]['message']['content']'As of my last update in October 2023, "fastspec" is not widely known or associated with a specific standalone technology, product, or concept in public discourse. However, the term could refer to various topics depending on the context, such as:\n\n1. **Software or Libraries**: It might denote a'
resp = await oai_cli.audio.create_speech(model="tts-1", input="Hello from fastspec!", voice="alloy")
Path("hello.mp3").write_bytes(resp)
print(f"Saved {len(resp)} bytes to hello.mp3")Saved 26400 bytes to hello.mp3
resp = await oai_cli.audio.create_transcription(
file=open("hello.mp3", "rb"), model="gpt-4o-transcribe", stream=True)
async for ev in resp: print(ev.get('delta', ''), end='')Hello from Fastbec.
Google Discovery specs use nested resource groups with attribute chaining:
gem_cli = OpenAPIClient(gem_spec, headers={"x-goog-api-key": os.environ["GEMINI_API_KEY"]})
str(gem_cli.models)[:500]'- models.generate_content(model, contents, system_instruction, tools, tool_config, safety_settings, generation_config, cached_content, service_tier, store): *Generates a model response given an input `GenerateContentRequest`. Refer to the [text generation guide](https://ai.google.dev/gemini-api/docs/text-generation) for detailed usage information. Input capabilities differ between models, including tuned models. Refer to the [model guide](https://ai.google.dev/gemini-api/docs/models/gemini) and '
resp = await gem_cli.models.generate_content(
model="models/gemini-2.5-flash",
contents=[{"parts": [{"text": "What is fastspec?"}]}])
resp['candidates'][0]['content']['parts'][0]['text'][:200]'**FastSpec** is a Ruby Gem designed to significantly speed up your local RSpec test suite execution by intelligently identifying and running only the specs relevant to your recent code changes.\n\n### T'
Nested resource groups are accessed with attribute chaining:
gem_cli.tuned_models.permissions.createCreate a permission to a specific resource.
Parameters:
- parent (str, required): Required. The parent resource of the
Permission. Formats:tunedModels/{tuned_model}corpora/{corpus} - role (str, required): Required. The role granted by this permission.
- name (str, optional): Output only. Identifier. The permission name. A unique name will be generated on create. Examples: tunedModels/{tuned_model}/permissions/{permission} corpora/{corpus}/permissions/{permission} Output only.
- grantee_type (str, optional): Optional. Immutable. The type of the grantee.
- email_address (str, optional): Optional. Immutable. The email address of the user of group which this permission refers. Field is not set when permission’s grantee type is EVERYONE.
Route parameters (like {owner} and {repo}) are passed as regular function arguments:
resp = await gh_cli.repos.get(owner="AnswerDotAI", repo="fastcore")
resp['full_name'], resp['description'], resp['stargazers_count']('AnswerDotAI/fastcore', 'Python supercharged for the fastai library', 1101)
gh_cli.repos.getGet a repository
Docs: https://docs.github.com/rest/repos/repos#get-a-repository
Parameters:
- owner (str, required): The account owner of the repository. The name is not case sensitive.
- repo (str, required): The name of the repository without the
.gitextension. The name is not case sensitive.
fastspec clients can be made available to AI assistants via solveit’s python sandbox using allow(). Registering an op lets the sandboxed code call it (including the network access it needs); everything else stays blocked. Four levels of access are supported:
Single method access — lock down to specific operations:
allow(oai_cli.images.create_image)Single group access — only specific groups:
allow(oai_cli.chat)Single client access — all groups on one specific client:
allow(oai_cli)Full API access — every operation on every client (any OpFunc may run):
allow({OpFunc: ['__call__']})