Systematic API Interaction for AI Agents
Systematic API Interaction for AI Agents

My recent work requires giving AI agents consistent, secure, and configurable access to external APIs. This led me to build Aperture, a command-line tool written in Rust that transforms OpenAPI specifications into executable, self-documenting API clients. You can find the open-source project on GitHub.

The challenge begins when you need an agent to perform a real-world task like checking the status of an issue, creating a ticket, or querying usage metrics. The approach of writing ad-hoc scripts for each integration creates systemic rot in any codebase: untracked security credentials scattered across environment files, zero discoverability of available operations, brittle error handling that fails silently, and a cognitive overhead that scales linearly with each new API. For an automated agent that needs programmatic certainty and proper context management, this is architectural quicksand.

A more structured approach involves protocols like MCP (Model Context Protocol), which provides a powerful client-server architecture for connecting models to tools. This is an excellent solution for stateful, persistent integrations. However, for many tasks involving single, ephemeral API calls, the overhead of maintaining a running server process is disproportionate to the task.

I needed something different, a universal API adapter with the rigor of a protocol but the ergonomics and portability of a CLI tool. The insight was to stop writing one-off clients and instead build a system that generates the client dynamically, based on the API’s blueprint.

The Blueprint and the Action

The core philosophy of Aperture is the separation of the API’s specification from its execution. For almost every modern API, a machine-readable blueprint already exists in its OpenAPI specification. This document is the API’s source of truth. Most developers see it as documentation. It can be seen as a program waiting to be executed.

Aperture works in two distinct stages:

  1. Cataloging the API: You point Aperture at an OpenAPI specification just once. It ingests the document, validates it, and transforms it into a lean, optimized binary cache. The API is now cataloged.
  2. Executing a Command: When you (or an agent) run a command, Aperture loads the cached blueprint in milliseconds. It knows precisely how to construct the HTTP request, which parameters are required, and how to apply the correct security credentials.

Security Model

Aperture’s security model has been designed to solve a real-world problem: how do you use unmodified, third-party OpenAPI specifications while maintaining secure credential management?

The solution is quite elegant. Instead of requiring modifications to the OpenAPI spec itself, Aperture provides a dedicated command for secret configuration:

# Configure the secret mapping once
aperture config set-secret sentry auth_token --env SENTRY_AUTH_TOKEN

This approach offers several advantages:

  • Use Unmodified Specs: You can directly use OpenAPI specifications from vendors without any modifications
  • Centralized Security: All secret mappings are managed through Aperture’s configuration, not scattered across spec files
  • Environment Flexibility: Secrets remain in environment variables, respecting twelve-factor app principles

When Aperture executes a command, it looks up the security scheme in the OpenAPI spec, checks its configuration for the corresponding secret mapping, retrieves the token from the specified source, and constructs the correct Authorization header. The entire flow is transparent to both the user and the agent.

Working with Real-World APIs

Real APIs are messy. They have deprecated endpoints, experimental features, and sometimes non-standard extensions that don’t perfectly align with the OpenAPI specification. Aperture embraces this reality.

Partial Specification Acceptance

The --strict flag embodies the philosophical view that perfect should not be the enemy of useful. By default, Aperture gracefully skips unsupported endpoints rather than rejecting an entire API specification. This means you can start using an API immediately, even if some endpoints use features Aperture doesn’t yet support.

# Load an API with experimental endpoints
aperture config add github https://api.github.com/openapi.json

# Aperture will skip unsupported endpoints and make the rest available
# Use --strict if you need guarantees that every endpoint works
aperture config add github https://api.github.com/openapi.json --strict

Per-API Environment Configuration

Enterprise environments demand flexibility. Different APIs live in different environments: development, staging, production. Aperture uses a sophisticated per-API system instead of a simple global configuration.

# Configure different base URLs for different environments
aperture config set-url myapi https://staging.api.example.com --env staging
aperture config set-url myapi https://api.example.com --env production

# The API uses the appropriate base URL based on the current environment
APERTURE_ENV=staging aperture api myapi get-users

Designed for Programmatic Certainty

Building this tool with an AI as the primary user forced a level of explicitness that eliminates ambiguity for all users. When you design for a non-human user, you cannot rely on context clues, implicit understanding, or error recovery through intuition. This constraint creates better software.

  • --describe-json: This flag outputs a structured JSON manifest of every available command, parameter, and option for a given API. It serves as a self-documenting contract that allows any program, agent or script, to discover capabilities without documentation.
  • --json-errors: Errors become data structures. An agent can parse {"error_type": "SecretNotSet", "env_var": "SENTRY_AUTH_TOKEN"} and take corrective action, unlike an ambiguous HTTP status code.
  • --dry-run: Deterministic preview of operations before execution, essential for both automated systems and careful humans.
  • Response Processing: Built-in JQ filtering and batch processing capabilities turn raw API responses into structured, actionable data.

A Universal API Adapter

Let’s see the complete workflow with a real API:

# One-time setup to add the API and configure authentication
aperture config add sentry https://raw.githubusercontent.com/getsentry/sentry-api-schema/main/openapi-derefed.json
aperture config set-secret sentry auth_token --env SENTRY_AUTH_TOKEN

# Set the secret in your environment
export SENTRY_AUTH_TOKEN="your_actual_token"

# Now any agent or developer can discover and use the API
aperture api sentry --describe-json  # Discover available operations
# Or
aperture list-commands sentry

# Execute commands
aperture api sentry events list-an-organizations-issues \
    --organization_id_or_slug my-org \
    --query "is:unresolved" \
    --limit 1

This approach eliminates entire categories of integration bugs: malformed requests, authentication failures, undiscovered endpoints, and brittle error handling. By building a universal adapter to APIs, Aperture enables both AI agents and developers to interact with external services through a consistent, discoverable, and robust interface.

Try Aperture Today

If you’re building AI agent workflows, managing multiple API integrations, or simply tired of writing the same HTTP client code repeatedly, Aperture offers a fundamentally different approach. Run cargo install aperture-cli, point it at your first OpenAPI spec, and experience what systematic API interaction feels like.

→ Get started with Aperture on GitHub