Freestyle Docs

Freestyle / Guides

Select Anthropic Models and Inject Authentication

Call Anthropic Messages from a sandbox with edge-injected credentials, select a Claude model in your controller, and rotate model access.

Run an Anthropic client inside a VM while keeping its real API key in your trusted controller. An HTTP egress rule injects the key and overwrites the top-level model on POST /v1/messages. The rest of the Messages request and the response keep Anthropic’s format.

This recipe uses direct api.anthropic.com API-key authentication. Bedrock, Vertex, interactive Claude login, and their credential flows require their own integrations.

Prepare The Controller And Snapshot

Run the controller outside the sandbox. Install the SDK with npm install freestyle@latest and supply these environment variables there:

  • FREESTYLE_API_KEY: your Freestyle API key.
  • FREESTYLE_SNAPSHOT_ID: an Ubuntu-based snapshot containing your application, Node.js, ca-certificates, and @anthropic-ai/sdk.
  • ANTHROPIC_API_KEY: the provider key to inject.
  • ANTHROPIC_MODEL: the Claude model ID to select.

Install @anthropic-ai/sdk in your application’s directory with npm install @anthropic-ai/sdk before taking the snapshot. Install dependencies while the builder has network access; the application VM below starts with an empty firewall. Keep provider and Freestyle credentials out of the snapshot.

Choose a model from Anthropic’s model catalog that your key can access and that supports your tools, input types, and thinking settings. Use a key scoped to the intended workspace. A multi-workspace key also needs a controller-injected anthropic-workspace-id, as described in Anthropic authentication.

Inject The Key And Select A Model

Save this as anthropic-controller.ts outside the VM:

import { Freestyle, type CreateTlsRuleOptions } from "freestyle";

function requiredEnv(name: string): string {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`Missing ${name}`);
  return value;
}

const freestyle = new Freestyle();
const { vmId } = await freestyle.vms.create({
  snapshotId: requiredEnv("FREESTYLE_SNAPSHOT_ID"),
  firewall: { rules: [] },
});

function anthropicRule(apiKey: string, model: string): CreateTlsRuleOptions {
  return {
    action: "allow",
    domain: "api.anthropic.com",
    source: { vmId },
    destination: { public: true },
    match: { method: ["POST"], path: { exact: "/v1/messages" } },
    transform: [
      { headers: { "x-api-key": apiKey } },
      { jsonPatch: [{ op: "add", path: "/model", value: model }] },
    ],
  };
}

const rule = await freestyle.tls.rules.create(
  anthropicRule(requiredEnv("ANTHROPIC_API_KEY"), requiredEnv("ANTHROPIC_MODEL")),
);
const ruleId = rule.id;
console.log({ vmId, ruleId }); // Retain these IDs in your controller.

The edge replaces the guest’s x-api-key and sends the request to Anthropic over a separate HTTPS connection with certificate verification. JSON Patch add creates /model if missing and overwrites it if present. Header secrets and patch values are sealed and read back as "***".

Anthropic supports x-api-key and Bearer authentication. This example pairs an x-api-key transform with an SDK client using that same header. For a Bearer client, inject an authorization header containing the real Bearer credential instead, and configure the client to send only a placeholder Bearer credential. Freestyle overwrites named headers; it does not remove other authentication headers automatically.

Call Messages Inside The VM

Place this file in the directory containing the installed provider SDK:

import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic({
  apiKey: "unused",
  authToken: null,
  baseURL: "https://api.anthropic.com",
});

const message = await anthropic.messages.create({
  model: "selected-by-freestyle",
  max_tokens: 256,
  messages: [{ role: "user", content: "Reply with one sentence about Linux sandboxes." }],
});
console.log(message.model);
for (const block of message.content) {
  if (block.type === "text") console.log(block.text);
}

Run inside the VM, after the rule has configured its hostname and trust store:

NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt node call-anthropic.mjs

The system bundle includes Freestyle’s installed CA. Node reads NODE_EXTRA_CA_CERTS at startup; restart an existing process after trust-store changes. Other distributions may use a different bundle path. Keep TLS verification enabled.

For Docker, use the container TLS recipe with api.anthropic.com (or your alias). The VM’s CA, environment variables, and hostname mappings are not inherited; NODE_EXTRA_CA_CERTS must point to the mounted bundle inside the container. Python clients have separate Requests and HTTPX settings.

The official SDK adds /v1/messages to the base URL and supplies anthropic-version and content-type: application/json. Do not append /v1 to this SDK’s base URL. authToken: null prevents a Bearer token from being inherited from the environment. The placeholder model and API key are replaced at the edge.

For SSE, set stream: true on messages.create and iterate the returned stream with for await (const event of stream). The request remains JSON and the response streams through. Beta query parameters do not change the exact-path match; client-supplied anthropic-beta headers are preserved.

Choose The Scope Of Model Access

The rule transforms only POST /v1/messages. A request such as POST /v1/messages/count_tokens or GET /v1/models is forwarded with its original headers and body, so it receives no real key from this rule. match selects transforms; it does not deny unmatched endpoints.

Keep the VM firewall restricted and review other TLS grants. The grant is available to every process in the VM. It does not identify a particular agent or executable.

For token counting, configure a separate domain alias with an exact /v1/messages/count_tokens match and point a separate SDK client at it. See alias routing. Two rules for the same domain/source with different match values do not dispatch by path: TLS rule selection happens first.

To let the guest select its own model, remove only the jsonPatch entry from anthropicRule and pass a real model ID to the guest SDK. Keep the method/path match to retain the Messages-only credential scope. Dropping match too injects the credential into every HTTP request selected by that TLS rule.

The Messages API schema still applies. Changing /model does not translate thinking parameters or tools, constrain optional fallback models, or rewrite the models in nested batch entries. A Messages-only rule also does not cover multipart files or all requests made by Claude Code. Inventory those requests before adapting the Claude Code sandbox guide.

Rotate The Key Or Change The Model

After changing the controller’s secret or selected model, replace the full rule using the same function and saved ruleId:

await freestyle.tls.rules.update(
  ruleId,
  anthropicRule(requiredEnv("ANTHROPIC_API_KEY"), requiredEnv("ANTHROPIC_MODEL")),
);

Include the real secret, model, and match conditions on every update. Redacted API reads cannot reconstruct them. Cached decisions can take about five seconds to refresh; requests already in flight continue with the previous values. Verify a fresh request before revoking an old credential. Freestyle does not renew expiring provider credentials automatically.

When the VM no longer needs this grant:

await freestyle.tls.rules.delete(ruleId);

This removes the grant after cache refresh. Revoke the key at Anthropic if the credential itself should stop working, and delete the VM separately when done. Other TLS and firewall grants continue to apply.

Verify A Request

Make a small real request, inspect message.model, and check provider usage. A model alias can resolve to a versioned ID. Change the selected model in the controller and confirm a subsequent request after refresh. Requests are billed normally by your provider.

For 401, check that the request uses the matched method/path and that the guest and transform use the same authentication header. Check workspace scope and model access at Anthropic. For 400, check max_tokens, tools, thinking settings, and required headers against the selected model. For certificate failures, check the guest CA bundle.

The edge accepts bounded, uncompressed JSON for patched requests; malformed JSON and failed patches are rejected before forwarding. See JSON transform limits for status codes and body limits. Transforms do not provide provider token refresh, spending limits, or a complete model gateway policy.

esc