Aperture by Tailscale
Last validated:
During the alpha testing period, Aperture by Tailscale is available at no additional cost across all Tailscale plans. Request access at aperture.tailscale.com.
Like the Tailscale Personal plan, Aperture by Tailscale comes with similar usage limits of three free users. Contact us for pricing if you need more than three users.
As organizations adopt AI across development, automation, and internal tools, they face new challenges around security, visibility, and control. API keys are often scattered across developer machines, CI systems, and automated agents, increasing the risk of leaks and making credentials hard to rotate or audit. At the same time, teams lack clear insight into who is using which models, how frequently they are used, and what those interactions cost. This makes it difficult for security, platform, and compliance teams to support AI usage at scale without slowing developers down.
Aperture by Tailscale addresses these challenges by providing a centralized AI gateway inside a Tailscale network, or tailnet. Instead of distributing API keys, users and automated agents authenticate using their existing Tailscale identity. Aperture securely injects provider credentials on their behalf and routes requests to multiple upstream LLM providers such as OpenAI, Anthropic, and Google, without requiring changes to existing tools or workflows.
Because all AI requests flow through Aperture, organizations gain consistent visibility into AI usage. Aperture captures telemetry, tracks sessions, and extracts useful metadata such as token usage and tool calls. This enables auditing, cost awareness, and operational insight, while allowing teams to safely adopt AI tools and agents with the same identity, access, and observability principles they already use for the rest of their infrastructure.
Refer to the Aperture by Tailscale configuration reference for detailed information about all available settings.
Requirements
Before deploying Aperture, ensure your environment meets these requirements.
-
A Tailscale network
Aperture runs exclusively on tailnets. This is a fundamental requirement because the entire authentication model depends on Tailscale identity.
User identity comes automatically from tailnet membership. When a client connects to Aperture, the proxy queries Tailscale to identify the user by their login name and device. This enables API access without sharing keys with users because Aperture centrally manages them and injects them when forwarding requests.
If your organization does not use Tailscale, Aperture will not work for you. But you can configure Aperture to monitor devices outside the tailnet.
-
One or more LLM provider accounts
You need API keys for any LLM providers to route through Aperture. Aperture supports the following providers:
- OpenAI (GPT-4o, o1, o3-mini, and other models)
- Anthropic (Claude Sonnet, Claude Opus, and other models)
- Google (Gemini models)
- OpenRouter (provides access to open source models such as DeepSeek, Qwen, and others)
- OpenAI-compatible APIs (for example, llama.cpp and other self-hosted endpoints)
Use cases
Aperture addresses several common challenges organizations face when adopting LLM tools.
-
Centralized API key management
Distributing API keys to developers creates security risks and an administrative burden. Keys get committed to repositories, shared insecurely, or forgotten when employees leave.
Aperture centralizes API keys in server configuration. Clients connect through the proxy without needing provider credentials. User identity comes from tailnet membership.
-
Visibility into LLM usage
Engineering teams adopt LLM-powered tools without centralized tracking. Individual developers use different providers, models, and tools. Without aggregation, organizations cannot answer questions such as:
- How many tokens did we use last month?
- Which models are teams using?
- What does the breakdown of our LLM spend look like?
Aperture captures every request with user attribution, model identification, and token counts. The dashboard aggregates this data by user, model, and time period.
-
Adoption analytics
Platform teams deploying LLM tools need to understand adoption and answer questions such as:
- Which teams are using the tools? How frequently?
- Are there users who tried a tool one time and stopped?
The adoption dashboard shows organization-wide usage patterns, active users over time, and usage distribution histograms.
-
Cost tracking
LLM API costs scale with token usage, but tracking consumption across multiple tools and providers requires aggregating data from each source individually. Developers might not realize how much their workflows cost, and finance teams lack the data to forecast budgets.
Aperture extracts token usage from every response, including input, output, cached, and reasoning tokens. This data feeds into dashboards and exports for cost analysis.
-
Compliance and audit trails
Regulated industries require audit trails for AI interactions. When LLM requests flow directly from client to provider, organizations have no record of what was sent or received.
Aperture stores full request and response bodies. The capture system preserves headers, payloads, and tool use data. Export logs and events to your SIEM for monitoring, retention, and compliance workflows. Aperture supports S3 export, which integrates with most security and compliance tooling.
-
LLM session debugging
You can use Aperture to debug LLM interactions by reviewing full request and response data. The Logs page groups related requests into sessions, letting you trace the flow of a conversation or coding task.
How it works
This section explains the core mechanisms that power Aperture.
Identity through Tailscale
Traditional API proxies require clients to authenticate with tokens or API keys. Aperture eliminates this step by using Tailscale's identity layer.
Every connection to a tailnet carries cryptographic proof of identity. When a request arrives at Aperture, the proxy queries Tailscale with the remote IP address. Tailscale responds with the user's login name (for example, alice@example.com), a persistent device identifier, and any tags assigned to that device.
This identity is trustworthy because it comes from Tailscale's control plane, not from the client. A user cannot forge their identity without compromising Tailscale's key exchange.
The identity flows through the entire system. Every metric record includes the login name and device ID. The dashboard filters by user. Access control uses roles derived from Tailscale tags.
Request routing by model
When a request arrives, Aperture extracts the model name from the request body (for example, claude-sonnet-4-20250514 or gpt-4o). The proxy looks up which provider serves that model and forwards the request to that provider's API endpoint, injecting the correct authentication headers.
From the client's perspective, the proxy appears as if it were the LLM provider itself. Clients connect to the proxy URL and send standard API requests. The proxy handles the routing transparently.
The following table lists the supported API formats:
| Format | Endpoint | Providers |
|---|---|---|
| OpenAI Chat | POST /v1/chat/completions | OpenAI, OpenRouter, llama.cpp |
| OpenAI Responses | POST /v1/responses | OpenAI |
| Anthropic Messages | POST /v1/messages | Anthropic |
| Gemini | POST /v1beta/models/{model}:generateContent |
More supported API formats will become available in the future, including AWS Bedrock and Google Vertex.
Telemetry capture
The capture system records everything needed to reconstruct and analyze each LLM interaction:
- Request data: HTTP method, path, headers (with sensitive values redacted), and full request body.
- Response data: Status code, headers, and full response body.
- Extracted metrics: Token counts by type (input, output, cached, and reasoning), model name, request duration, and tool use count.
- Session context: Session ID linking related requests with supported providers and agents such as Claude Code and Codex.
The proxy processes telemetry asynchronously after the response completes, so clients receive responses without significant delay.
The proxy handles two technical challenges transparently:
- Compression: The proxy decompresses response bodies that arrive compressed (
gzip,deflate, orbrotli) before storage. - Streaming: For streaming responses (Server-Sent Events), the proxy reconstructs a complete response object from the event stream for consistent metric extraction.
Session tracking
A single coding task or conversation typically involves many LLM requests. A developer using Claude Code can generate 50 or more requests while debugging one issue. Without grouping, these appear as 50 unrelated events. With session tracking, they form a coherent unit you can analyze as a whole.
Aperture groups related requests into sessions by detecting session identifiers from different client types. The following table describes how each client type identifies sessions:
| Client type | How sessions are identified |
|---|---|
| Claude Code | Session ID included in each request body |
| OpenAI Codex | Session ID sent as HTTP header |
| OpenAI Chat | Fingerprint generated from conversation content |
| Other clients | Random identifier assigned |
Sessions enable conversation-level analysis. The logs page groups requests by session, showing the full context of a coding session or chat conversation. You can review token costs per conversation rather than per request, trace how a coding assistant broke down a task, or identify which conversation consumed the most resources.
Get started
Aperture by Tailscale is a managed service that Tailscale hosts and maintains. To set up Aperture in your tailnet, visit aperture.tailscale.com.
After you have access to Aperture:
- Configure Aperture with the appropriate providers, models, and settings.
- Grant users access to Aperture.
- Set up LLM clients to direct traffic through the Aperture proxy.
- (Optional) Test your Aperture configuration.
Configure Aperture settings
To configure Aperture settings, go to the Aperture web interface. The web interface is available to devices in the same tailnet at http://ai/ui.
- Open the Settings page of the Aperture web interface.
- Update the configuration file to include the LLM providers and models you plan to use.
- Configure any additional settings.
The following code block contains a minimal configuration file example that sets up Aperture with access to Anthropic's Claude Sonnet 4.5 and Claude Opus 4.5 models.
{
"providers": {
"anthropic": {
"baseurl": "https://api.anthropic.com",
"models": [
"claude-sonnet-4-5",
"claude-opus-4-5",
],
"authorization": "x-api-key",
"compatibility": {
"anthropic_messages": true,
}
}
}
}
If you don't specify a configuration file, Aperture uses the default configuration. Refer to the Aperture configuration reference for details on all available settings.
Grant users access to Aperture
To let users in your tailnet access Aperture, ensure your tailnet access controls grant them access to connect to the Aperture instance.
- Log in to the Tailscale admin console.
- Go to the Access controls page of the admin console.
- Ensure your access control rules allow users to connect to the Aperture device.
For example, the following rule permits all users in the group group:ai-users access to the Aperture instance with the hostname ai:
{
// Define the ai-users group in the "groups" section of the access control rules.
"groups" : [
"group:ai-users": [
"dave@example.com",
"alice@example.com"
]
],
// Set the hostname of your Aperture instance to "ai" in the "hosts" section of the access control rules.
"host": [
"ai": "<YOUR_APERTURE_IP_ADDRESS>"
],
// Create a grant access control rule that allows users in the "group:ai-users" group to access the "ai" host on TCP ports 80, 443, and all ICMP types.
"grants": [
{
"src": [
"group:ai-users",
],
"dst": ["ai"],
"ip": ["tcp:80", "tcp:443", "icmp:*"]
}
]
}
Alternatively, you can use Tailscale tags to manage access to the Aperture instance. Assign a tag (for example, tag:ai) to the Aperture device and create an a grant rule that allows users in the ai-users group to access tag:ai.
Set up LLM clients to use Aperture
To avoid unexpected TLS issues, we strongly recommend using http:// for the Aperture URL when configuring LLM clients. For example, use http://ai instead of https://ai. All connections remain encrypted using WireGuard, even when HTTPS is not used.
After Aperture is running, configure your LLM clients to send requests through the proxy rather than directly to the providers. Aperture works with any coding agent or AI tool that lets you configure a custom base URL.
Aperture works with the following clients:
- Claude Code
- Codex
- Gemini CLI
- Roo Code
- Cline
In general, to configure an LLM client to use Aperture, you need:
- The URL of your Aperture proxy instance.
- Access to the LLM client configuration settings.
For the Aperture URL, you can use the Aperture hostname or its tailnet domain name. For example, if you set the hostname of your Aperture instance to ai in your Tailscale access controls, you can use either of the following URLs to access it:
- Hostname:
http://ai - FQDN:
http://ai.your-tailnet.ts.net
You can choose any name for your Aperture instance. It doesn't need to be ai.
The Aperture URL works the same way as the provider's base URL. For example, to access http://provider-url/v1/some-endpoint, you would use http://ai/v1/some-endpoint.
Claude Code
To configure Claude Code to use Aperture, create or edit the global Claude Code settings file (~/.claude/settings.json) to use the Aperture URL as the ANTHROPIC_BASE_URL. The following example include some additional recommended settings:
{
"apiKeyHelper": "echo '-'", // Placeholder; Aperture injects credentials
"env": {
"ANTHROPIC_BASE_URL": "http://ai", // Required: Aperture URL
}
}
The apiKeyHelper setting returns a placeholder value because Aperture automatically injects credentials.
Claude Code automatically routes requests through Aperture. No API key configuration is needed on the client because Aperture injects credentials.
You can also configure Claude Code using environment variables instead of a settings file. Set the following environment variables:
export ANTHROPIC_BASE_URL="http://ai"
Claude Code v1.x requires additional configuration because the apiKeyHelper setting does not exist in earlier versions. You must provide a placeholder authentication token and explicitly specify the model.
{
"model": "claude-sonnet-4-5", // Required: Specify a model
"env": {
"ANTHROPIC_AUTH_TOKEN": "bearer-managed", // Required: Placeholder token
"ANTHROPIC_BASE_URL": "http://ai", // Required: Aperture URL
}
}
Claude Code with Amazon Bedrock
To use Claude Code in the CLI with Aperture through Amazon Bedrock, add the following to your settings.json:
{
"env": {
"ANTHROPIC_MODEL": "claude-sonnet-4-5",
"ANTHROPIC_BEDROCK_BASE_URL": "http://ai/bedrock",
"CLAUDE_CODE_USE_BEDROCK": "1",
"CLAUDE_CODE_SKIP_BEDROCK_AUTH": "1"
}
}
To use Claude Code with VS Code and Bedrock you need to do the above and add the following to your VS Code user settings JSON:
- Open the Command Palette.
- Select Preferences: Open User Settings (JSON) (or Preferences: Open Workspace Settings (JSON), if applicable).
- Add
"claudeCode.disableLoginPrompt": true. - Save your changes.
Claude Code with Vertex AI
To use Claude Code with Aperture through Vertex AI, add the following to your settings.json:
{
"env": {
"CLOUD_ML_REGION": "global",
"ANTHROPIC_VERTEX_PROJECT_ID": "YOUR_PROJECT_ID",
"CLAUDE_CODE_USE_VERTEX": "1",
"CLAUDE_CODE_SKIP_VERTEX_AUTH": "1",
"ANTHROPIC_VERTEX_BASE_URL": "http://ai/v1"
}
}
To use Claude Code with VS Code and Vertex you need to do the above and add the following to your VS Code user settings JSON:
- Open the Command Palette.
- Select Preferences: Open User Settings (JSON) (or Preferences: Open Workspace Settings (JSON), if applicable).
- Add
"claudeCode.disableLoginPrompt": true. - Save your changes.
Codex
To configure Codex to use Aperture, create or edit your Codex configuration file (~/.codex/config.toml) to use the Aperture URL as the base_url and set the model to a Codex-compatible model. The following example shows how to set the base URL and includes some additional recommended settings:
model = "gpt-5.2-codex"
model_provider = "llm-ai-ts-net"
model_reasoning_effort = "high"
[model_providers.llm-ai-ts-net]
name = "Tailscale AI Gateway"
base_url = "http://ai/v1" # Required: Aperture URL
# Required to use "gpt-5-codex" model
wire_api = "responses"
The wire_api = "responses" setting configures Codex to use the OpenAI Responses API format.
Gemini CLI, Roo Code, Cline, and other clients
Configure the client to use the Aperture URL as the API base. Use the following settings:
- API Base URL:
http://ai - API Key: Leave empty or set to any value (Aperture ignores client-provided keys)
Custom applications
You can configure custom applications to use Aperture as long as they use a supported provider API. Point your HTTP client at the Aperture URL and use the standard provider API format. For example, to use the OpenAI chat completions API:
POST http://ai/v1/chat/completions
Content-Type: application/json
{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello"}]
}
Aperture routes the request to the appropriate provider based on the model name.
Test your Aperture configuration
You can test your connection to Aperture using curl commands from a device connected to the tailnet. The following examples show how to send test requests using different API formats.
Test Anthropic API format:
curl -s http://ai/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-haiku-4-5-20251001",
"max_tokens": 25,
"messages": [{"role": "user", "content": "respond with: hello"}]
}'
Test OpenAI API format:
curl -s http://ai/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "respond with: hello"}]
}'
Test OpenAI Responses API format:
curl -s http://ai/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"input": [{"role": "user", "content": "respond with: hello"}]
}'
If the requests succeed, Aperture is correctly routing to your configured providers. Visit your Aperture dashboard (http://ai/ui/) to access the requests in your usage history.
Web dashboard
You can access the Aperture dashboard at /ui/ on your Aperture hostname (for example, http://ai/ui/). The following sections describe each page in the dashboard.
Dashboard page
If you incorrectly authenticate user devices with tag identities, all LLM sessions from those devices appear to come from the tag identity instead of the individual user. This happens because Tailscale tags do not provide per-user identity. Refer to All sessions appear to come from the same user for more information.
The Dashboard page shows token usage for the current user. It includes the following components:
- Total tokens by model (bar chart)
- Recent activity timeline
- Quick stats: total requests, total tokens, active sessions
Administrators can access other users' dashboards by going to /ui/dashboard/:loginName where loginName is the Tailscale login name of the user (for example, alice).
Logs page
The Logs page displays request history with session grouping. It provides the following views:
- Sessions list: Shows aggregated metrics per session (total tokens, request count, and duration).
- Session expansion: Select a session to display its individual requests.
- Request detail: Select a request to display full capture data, including headers, request body, response body, and tool use details.
- Filtering: Filter by date range, model, or user.
Session grouping helps you understand conversation context. A Claude Code session can show dozens of requests that together represent a single coding task.
Tool Use page
The Tool Use page displays tool invocation patterns across sessions:
- Tool use histogram over time
- Tool type breakdown per session
- Filter by user, model, or specific tool names
Adoption page
The Adoption page shows organization-wide metrics. It displays the following data:
- Active users over time
- Usage distribution histogram (how usage is spread across users)
- Top users by token consumption
- Model popularity breakdown
This page helps platform teams understand LLM adoption patterns and identify users who need support or training.
Models page
The Models page lists all configured models with their provider information. Each model entry includes the following details:
- Model ID
- Provider name and description
- API compatibility (which endpoints the model supports)
All providers and models configured in the Aperture settings show on the Models page. Users can reference this page to choose which model to use through Aperture when configuring LLM clients.
Settings page
The Settings page lets administrators access and edit the Aperture configuration file.
How-to guides
Step-by-step instructions for performing specific tasks in Aperture.
Integrate Aperture with Oso
You can integrate Aperture with Oso:
- Go to the Aperture web interface at
http://ai/ui/. - Go to the Tool Use page, then select View Tool Use in Oso.
- Log in to your Oso account (or sign up) and create an API key. You'll need this key to configure the hook in Aperture.
- Go to the Settings page in the Aperture web interface.
- Add a grant that uses the Oso hook to specify what to send to Oso. Aperture only sends data to Oso when a request matches the grant conditions.
// Other Aperture configuration settings...
"temp_grants": [
// Example hook: send traffic to Oso if it matches certain parameters.
// You need to also configure Oso in the "hooks" section for this to work.
{
"src": [
// No users by default. Try "*" to capture everyone's traffic.
],
"grants": [
{
"hook": {
"match": {
"providers": ["*"],
"models": ["*"],
// Capturing only tool calls
"events": ["tool_call_entire_request"],
},
"hook": "oso",
"fields": ["user_message", "tools", "request_body", "response_body"],
},
},
],
},
],
- Add a hook for Oso in the
hookssection.
// Other Aperture configuration settings...
"hooks": {
"oso": {
"url": "https://api.osohq.com/api/agents/v1/model-request",
"apikey": "YOUR_OSO_API_KEY",
},
},
Monitor sessions on a device outside the tailnet
You can use Aperture to monitor LLM sessions from a device that's not in the same tailnet as the Aperture deployment using the experimental ts-plug and ts-unplug tools.
- The
ts-plugapplication exposes localhost to your tailnet. - The
ts-unplugapplication lets you access external tailnet services locally (without needing to connect to the tailnet).
The ts-unplug application lets you capture sessions from devices that are either in a different tailnet or not connected to a tailnet at all.
To set up ts-unplug on the device to capture LLM sessions from:
- Clone the
ts-plugrepository from GitHub. - Build the
ts-unplugapplication:- Run
make ts-unplug. - Run
make install.
- Run
- Run the
ts-unplugcommand to link to the Aperture proxy instance. You'll need to use the fully qualified domain name (FQDN) of the Aperture proxy and an available and accessible port number (for example, port9996).
ts-unplug -dir ./state -port <PORT_NUMBER> ai.<YOUR_TAILNET_ID>.ts.net
Next, you'll need to approve the device from the Machines of the admin console of the tailnet that's hosting Aperture. After you approve it, users on that device can access Aperture from http://localhost:<PORT_NUMBER>.
Limitations
Aperture has the following limitations you should consider before deployment. But keep in mind that we're actively developing and improving Aperture, so this list will update frequently.
-
Tailscale requirement
Aperture works exclusively on tailnets. The entire authentication model depends on Tailscale identity. But, you can use Aperture to monitor environments in another tailnet or not connected to a tailnet at all using ts-plug.
-
Provider support
Metrics extraction relies on parsing provider response formats. Aperture handles OpenAI, Anthropic, Gemini, and OpenAI-compatible APIs. New providers or format changes might require updates.
-
No request modification
The Aperture proxy captures and forwards requests without modification (except authentication headers). Features like rate limiting, request filtering, or prompt injection detection are not yet supported. These features are planned.
Troubleshooting
Use the following sections to diagnose and resolve common issues with Aperture.
All sessions appear to come from the same user
If you use tag-based identities to authenticate user devices, all LLM sessions from those devices appear to come from the same user. This happens because Tailscale tags do not provide per-user identity. The intended use of tags is service account or server device authentication, not individual user authentication. For example, you might use a tag identity for a fleet of PostgreSQL database servers. Refer to the tags documentation for more information.
To resolve this issue, ensure that users connect to Aperture from devices associated with their individual Tailscale accounts.
HTTPS connection issues
If you encounter HTTPS connection issues when accessing the Aperture web interface or using LLM clients, make you're using http:// and not https:// for the Aperture URL. For example, use http://ai/ui/ (replace ai with your Aperture hostname).
FAQ
This section answers common questions about Aperture.
What happens if a user tries to connect from outside the tailnet?
The connection fails at the network level. Aperture listens on Tailscale interfaces, so traffic from outside your tailnet never reaches the proxy. However, you can explicitly set up Aperture for devices outside the tailnet using ts-unplug.
What happens when I add a new LLM provider to the configuration?
Clients can immediately use models from that provider by specifying the model name in their requests. No client changes are required because the proxy routes based on model name.
What happens if a streaming response is interrupted mid-stream?
The proxy captures whatever data arrived before the interruption. Metrics extraction might fail or report partial data, but the proxy stores the partial capture for debugging.
Do clients need API keys to use Aperture?
No. Aperture identifies users through Tailscale and injects provider API keys automatically. Clients connect without credentials.
Can I use Aperture with providers not listed in the documentation?
Yes, if the provider uses an OpenAI-compatible API format. Configure it as a provider with openai_chat: true compatibility and the appropriate authorization type.
Can I use Aperture with self-hosted LLMs?
Yes, it's possible to proxy self-hosted LLMs with Aperture without exposing the endpoints to the public internet.
Can I use Aperture in CI/CD environments, such as GitHub Actions?
Yes, as long as it's possible to run Tailscale. Aperture works in common containerized environments like GitHub Actions without needing to expose either the agent or the gateway to the public internet.
Can I use Aperture with multiple tailnets?
Yes, you can use Aperture to monitor environments in another tailnet using ts-plug. You can also use ts-unplug to capture LLM sessions from environments that aren't connected to a tailnet at all.
