Freestyle Docs

Freestyle / Guides

Select OpenAI Models and Inject Authentication

Call OpenAI from a sandbox with an API key held at the edge, choose the model from your controller, and rotate credentials without rebuilding the VM.

Let code inside a VM call OpenAI while your trusted controller holds the real API key and selects the model. An HTTP egress rule replaces the authorization header and the request’s top-level model before forwarding it to OpenAI. The VM uses the official SDK with a placeholder key.

This recipe targets POST /v1/responses. It also shows how to adapt the rule for Chat Completions, allow the caller to choose a model, and rotate access.

For models accessed through OpenRouter, use the OpenRouter guide; its API path and fallback controls differ from direct OpenAI requests.

Prepare The Controller And Snapshot

Run the controller examples outside the sandbox. Install the SDK with npm install freestyle@latest, then supply these controller environment variables:

  • FREESTYLE_API_KEY: your Freestyle API key.
  • FREESTYLE_SNAPSHOT_ID: an Ubuntu-based snapshot containing your application, Node.js, ca-certificates, and the openai package.
  • OPENAI_API_KEY: the provider key to inject.
  • OPENAI_MODEL: the model ID your application should use.

Install the provider SDK in your application’s directory with npm install openai when preparing the snapshot. Package installation needs network access during preparation. The application VM below starts with an empty firewall and receives only the model hostname grant. Keep both API keys out of application files and snapshots.

Choose an ID from the OpenAI model catalog that supports the Responses API and your application’s inputs and tools. The provider key must have access to it. Test application behavior before changing models; rewriting model does not adapt unsupported parameters or tools.

Inject The Key And Select A Model

Save this as openai-controller.ts in your trusted controller:

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 openaiRule(apiKey: string, model: string): CreateTlsRuleOptions {
  return {
    action: "allow",
    domain: "api.openai.com",
    source: { vmId },
    destination: { public: true },
    match: { method: ["POST"], path: { exact: "/v1/responses" } },
    transform: [
      { headers: { authorization: `Bearer ${apiKey}` } },
      { jsonPatch: [{ op: "add", path: "/model", value: model }] },
    ],
  };
}

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

OpenAI uses Bearer authentication. The edge overwrites the guest’s Authorization header, then opens a separate HTTPS connection that verifies OpenAI’s certificate. Header secrets and patch values are sealed at rest and read back as "***".

JSON Patch add is an object upsert: /model is inserted if absent and overwritten if present. It is not a default that the guest can override. See JSON Patch semantics.

Call OpenAI Inside The VM

Place this file in the application directory that contains the installed SDK:

import OpenAI from "openai";

const openai = new OpenAI({
  apiKey: "unused",
  baseURL: "https://api.openai.com/v1",
  organization: null,
  project: null,
});

const response = await openai.responses.create({
  model: "selected-by-freestyle",
  input: "Reply with one sentence about Linux sandboxes.",
});
console.log(response.model);
console.log(response.output_text);

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

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

Freestyle installs its CA in the guest’s system trust store. This Ubuntu bundle path makes Node trust the additional CA at process startup. Use your distribution’s bundle path on other images, and restart existing Node processes after trust-store changes. Keep certificate verification enabled.

If the application runs in Docker, the VM’s CA, environment variables, and hostname mappings are not inherited. Use the Docker TLS recipe with api.openai.com (or your alias), and pass NODE_EXTRA_CA_CERTS using the container’s mounted bundle path. For Python clients, see the separate Requests and HTTPX settings.

The official OpenAI SDK sends JSON to /v1/responses; it accepts the placeholder strings locally. The edge replaces both before OpenAI receives the request. The explicit base URL and null organization/project options avoid inheriting unrelated controller configuration. If you require OpenAI-Organization or OpenAI-Project, set those headers in the controller’s header transform too.

For streaming, add stream: true to responses.create and iterate its returned stream with for await (const event of stream). The request is still JSON; the SSE response streams through without JSON rewriting.

Choose Which Requests Receive Credentials

The method and path must both match. Query parameters do not affect the path comparison. An unmatched request is forwarded without either transform; match does not deny it. For example, GET /v1/models keeps the guest’s placeholder credential and receives no injected key.

Keep the VM’s firewall restricted and review other TLS grants. Broad public egress can let a workload bypass the transformed route using credentials it obtains elsewhere. All processes in the selected VM can use this grant; it does not distinguish binaries.

For Chat Completions, change the rule’s exact path to /v1/chat/completions and use openai.chat.completions.create({ model, messages }). The same /model patch works. This replaces the Responses grant; it does not enable both endpoints.

To expose both with separate transforms, use distinct domain aliases and SDK base URLs, as shown in the OpenShell migration guide. Creating two rules with the same domain/source and different match values does not create a path router: matching happens after TLS rule selection.

To let the guest choose a model while keeping the key outside the VM, remove the jsonPatch entry from openaiRule. Retain match and the header transform, and supply a real model ID in the guest’s SDK request. Removing match too grants the injected credential to every HTTP request on the selected rule, including endpoints outside this recipe.

Change The Model, Rotate The Key, Or Remove Access

Using the same controller function and saved ruleId, replace the full rule:

await freestyle.tls.rules.update(
  ruleId,
  openaiRule(requiredEnv("OPENAI_API_KEY"), requiredEnv("OPENAI_MODEL")),
);

Load the new key or model into the controller before updating. Always include the actual secret and model value; a redacted API read cannot reconstruct them. Cached decisions can take about five seconds to refresh. Existing requests continue with their original configuration. Verify a new request succeeds before revoking the old provider key.

Delete the grant when it is no longer needed:

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

Deletion removes this grant after caches refresh; it does not revoke the key at OpenAI. Other firewall and TLS rules still apply. Delete the VM separately when the task is complete.

Verify The Integration And Handle Other API Shapes

Make a small real request and inspect response.model and provider usage. An alias may resolve to a versioned model ID. Change the controller’s model, wait for refresh, and verify a subsequent request. Provider requests use your account’s normal billing.

For a 401, check the method/path, injected key, and any required project headers. A certificate error calls for checking the guest CA bundle. A 400 or model-access error can indicate parameters incompatible with the selected model. See transform limits and errors for edge-generated failures.

This rule does not authenticate response retrieval, model discovery, file uploads, or batch requests. Multipart uploads, JSONL batches, and Realtime WebSocket sessions need their own handling. A top-level /model patch cannot control models inside uploaded batch files or impose a spending limit. Use provider permissions and an application gateway when you need those controls.

esc