Client routing
Customize the webapp URL
The SDKs guide users tohttps://www.braintrust.dev (or the BRAINTRUST_APP_URL variable) to view their experiments. In some
advanced configurations, you can reverse proxy traffic to the BRAINTRUST_APP_URL from the SDKs while pointing users to a different URL.
To do this, you can set the BRAINTRUST_APP_PUBLIC_URL environment variable to the URL of your webapp. By default, this variable is set to the value of BRAINTRUST_APP_URL, but you can customize it as you wish. This variable is only used to display information, so even its destination does not need to be accessible from the SDK.
- AWS
- GCP / Azure
Set it through the Deployments still served by the API Lambda (before module v6.0.0, or v6 with
braintrust_api_extra_env_vars passthrough map (Terraform module v6.0.0 or later):enable_ecs_api = false) set the same variable through service_extra_env_vars.APIHandler instead.Constrain SDKs to the data plane
If you’re self-hosting the data plane, you can also constrain the SDKs to only communicate with your data plane. Normally, they communicate with the control plane to:- Get your data plane’s URL
- Register and retrieve metadata (e.g. about experiments)
- Print URLs to the webapp
BRAINTRUST_APP_URL environment variable to the URL of your data plane and BRAINTRUST_APP_PUBLIC_URL to https://www.braintrust.dev (or the URL of your webapp).
- AWS
- GCP / Azure
Inbound traffic
Set HTTPS on the API load balancer
On AWS with the ECS API, an internal Application Load Balancer (ALB) fronts the API services. By default, the ALB serves plain HTTP on port 80 using its AWS-assigned DNS name. To serve HTTPS on a custom domain instead, set bothbraintrust_api_alb_certificate_arn and braintrust_api_alb_custom_domain (available in Terraform module v6.0.0 or later):
https://<braintrust_api_alb_custom_domain>. The certificate must cover the custom domain, and the domain must resolve to the ALB.
These two variables must both be set or both be null. Setting only one fails at plan time.
Set the HTTP keep-alive timeout
When the API server runs behind a load balancer, you may need to configure the HTTP keep-alive timeout to prevent connection resets. Load balancers typically have an idle timeout for connections, and if the API server’s keep-alive timeout is shorter than the load balancer’s timeout, the API server closes the connection while the load balancer still considers it open. When the load balancer tries to reuse that backend connection, it encounters a closed socket, resulting in connection reset errors and 502 responses. The API server exposes the following environment variable to configure the keep-alive timeout:TS_API_KEEP_ALIVE_TIMEOUT_SECONDS: The HTTP keep-alive timeout in seconds. Default:65
- AWS
- GCP / Azure
Set it through the
braintrust_api_extra_env_vars passthrough map (Terraform module v6.0.0 or later). This applies to the ECS API services:Set the CloudFront origin timeout
On AWS, requests are served through CloudFront, which closes a connection and returns504 Gateway Timeout if the origin takes too long to respond. Long-running scorers or tools invoked through /function/invoke can exceed the default 60-second origin read timeout. Raise it with the cloudfront_origin_read_timeout Terraform variable (available in Terraform module v5.3.0 or later):
Set inbound request rate limits
The API server can rate-limit log ingestion, SQL queries, and function invocation. Configure each surface separately with its own environment variables for limits, window length, and enforcement. All three surfaces behave the same way in these respects:- Windows: Each limit uses a fixed window that starts when the first matching request is counted, not on a clock boundary. The counter resets after the configured number of seconds, and the next matching request starts a new window. Rejected requests still count toward the limit.
- Enforcement: With enforcement disabled, requests over a limit are allowed and the API server logs a warning. With enforcement enabled, they fail with HTTP 429 and a
Retry-Afterheader, and the response body reports the configured limit, the window length, and the requests consumed. A limit of0is a real zero-request limit, so with enforcement enabled every matching request is rejected. - Replicas: Each limit applies across all API server replicas combined.
- Restarts: Rate limit configuration is read once at process start. Restart or redeploy the API services after changing any of these variables.
None of these variables has a dedicated Terraform variable or Helm value, so pass them through your deployment’s environment variable map, as shown in the examples below. Variables that take
<id>=<max_requests> pairs accept a comma-separated list of pairs.Limit log ingestion
Log ingestion limits apply per organization and per project, and both are disabled by default. A project limit replaces the organization limit rather than adding to it, so a project with its own entry ignores the organization limit entirely. With no limit configured, ingestion is uncapped. Window and enforcementRATELIMIT_API_LOGS_ORG_WINDOW_SECS: Window length in seconds. Default60. Despite the name, this also sets the window for project-based limits.RATELIMIT_API_LOGS_ORG_ENFORCE: Return HTTP 429 when a limit is exceeded. Defaultfalse(log a warning and allow the request). Despite the name, this also governs the enforcement of project-based limits.
RATELIMIT_API_LOGS_ORG: Per-organization limits, as<org_id>=<max_requests>pairs. Find an organization’s ID in the organization switcher.
Project-scoped limits require data plane v2.2.1 or later.
RATELIMIT_API_LOGS_PROJECT: Per-project limits, as<project_id>=<max_requests>pairs. Find a project’s ID under Settings > General.RATELIMIT_API_LOGS_PROJECT_DEFAULT: Limit for every project without an entry inRATELIMIT_API_LOGS_PROJECT. Set it only if you want every project capped.
- AWS
- GCP / Azure
Set these variables through the Deployments still served by the API Lambda (before module v6.0.0, or v6 with
braintrust_api_extra_env_vars passthrough map (Terraform module v6.0.0 or later). For example:enable_ecs_api = false) set the same variables through service_extra_env_vars.APIHandler instead.Limit SQL queries
SQL query limits apply per organization and per project, and both are disabled by default. A project limit adds to the organization limit rather than replacing it, so it can only tighten the effective limit. Organizations without their own entry fall back toRATELIMIT_BTQL_DEFAULT, and with no default configured, queries are uncapped.
Window and enforcement
RATELIMIT_BTQL_WINDOW_SECS: Window length in seconds. Default60.RATELIMIT_BTQL_ENFORCE: Return HTTP 429 when a limit is exceeded. Defaultfalse(log a warning and allow the query).
RATELIMIT_BTQL_ORG: Per-organization limits, as<org_id>=<max_queries>pairs. Find an organization’s ID in the organization switcher.RATELIMIT_BTQL_DEFAULT: Limit for every organization without an entry inRATELIMIT_BTQL_ORG.RATELIMIT_BTQL_DEFAULT_FUNCTIONS: Separate default for queries against prompts and functions. Defaults to 20 timesRATELIMIT_BTQL_DEFAULT, including when set to-1.
Project-scoped limits require data plane v2.2.1 or later.
RATELIMIT_BTQL_PROJECT: Per-project limits, as<project_id>=<max_queries>pairs. Setting one above the organization limit has no effect. With no organization limit configured, the project limit is the only one that applies. Find a project’s ID under Settings > General.
- AWS
- GCP / Azure
Set these variables through the Deployments still served by the API Lambda (before module v6.0.0, or v6 with
braintrust_api_extra_env_vars passthrough map (Terraform module v6.0.0 or later). For example:enable_ecs_api = false) set the same variables through service_extra_env_vars.APIHandler instead.These limits apply to queries from the API, the SDKs, and the MCP server. Queries issued from the Braintrust UI are exempt.
Limit function invocation
Invocation limits cover a project’s prompts, scorers, tools, and other custom code functions. They are project-scoped, and there is no organization-scoped limit. Two independent limits apply: a per-project limit that is disabled by default, and a per-function cap that is on by default with a fixed 10-second window and always returns HTTP 429. Window and enforcementRATELIMIT_INVOKE_WINDOW_SECS: Window length in seconds for the project limits. Default10. It does not affect the per-function cap.RATELIMIT_INVOKE_ENFORCE: Return HTTP 429 when a project limit is exceeded. Defaultfalse(log a warning and allow the invocation). It does not affect the per-function cap.
Project-scoped limits require data plane v2.2.1 or later.
RATELIMIT_INVOKE_PROJECT: Per-project limits, as<project_id>=<max_invocations>pairs. The count covers every function in the project, across all API keys. Find a project’s ID under Settings > General.RATELIMIT_INVOKE_PROJECT_DEFAULT: Limit for every project without an entry inRATELIMIT_INVOKE_PROJECT. Set it only if you want every project capped.
INVOKE_RATE_LIMIT_PER_10S: Maximum invocations per function, per API key, in a 10-second window. Default10000. The count is per function, not per project.ENABLE_INVOKE_RATE_LIMIT: Whether invocation rate limiting runs at all. Defaulttrue. Setting it tofalseturns off the per-function cap and the project limits.
- AWS
- GCP / Azure
Set these variables through the Deployments still served by the API Lambda (before module v6.0.0, or v6 with
braintrust_api_extra_env_vars passthrough map (Terraform module v6.0.0 or later). For example:enable_ecs_api = false) set the same variables through service_extra_env_vars.APIHandler instead.Preprocessors are exempt from invocation rate limits.
Outbound traffic
Secure outbound requests
The data plane makes outbound requests both to Braintrust and to URLs you or your users supply, such as webhooks, remote scorers, and integrations. Allow traffic to Braintrust through your firewall If you restrict outbound network traffic, allow the data plane to reach Braintrust at:gateway.braintrust.dev is the Braintrust-hosted Gateway. Two kinds of request go there:
- Built-in model requests. Braintrust serves built-in models itself, so requests for them always go to the hosted Gateway. Self-hosted organizations have built-in models off by default and opt in explicitly, and Topics requires them.
- LLM calls from user-authored code, such as custom scorers and tools, on AWS deployments that run the API on ECS (
enable_ecs_api = true) with Terraform module v6.4.0 or earlier. Module v6.5.0 and later sends these to the deployment’s own AI proxy instead, so that traffic stays in your AWS account.
Set outbound request rate limits
The Braintrust API server can rate-limit the outbound requests it makes to external domains, such asBRAINTRUST_APP_URL. Rate limiting prevents unintentionally overloading an external domain, which might otherwise block the API server’s IP in response. It is disabled by default. When enabled, requests are counted per API auth token per destination domain within a rolling window.
OUTBOUND_RATE_LIMIT_MAX_REQUESTS: The maximum number of requests per window. Default0, which disables rate limiting. Set a value greater than0to enable it.OUTBOUND_RATE_LIMIT_WINDOW_MINUTES: The window length in minutes before the count resets. Default1.
- AWS
- GCP / Azure
Use the dedicated variables (Terraform module v1.0.0 or later):
Connect to internal resources over VPC
On AWS, to connect Braintrust’s VPC to other internal resources (like an LLM Gateway), use one of the following approaches:- Create a VPC Endpoint Service for your internal resource, then create a VPC Interface Endpoint inside the Braintrust “Quarantine” VPC.
- Set up VPC peering with the Braintrust “Quarantine” VPC.
Braintrust Gateway
The Braintrust Gateway gives your applications a single OpenAI-compatible API for reaching any supported model provider (OpenAI, Anthropic, Google, AWS, and others), using provider keys you manage centrally in Braintrust. At request time it adds completions caching, provider failover, streaming, rate limiting, usage tracking, and trace logging. Braintrust hosts a Gateway atgateway.braintrust.dev. Self-hosted deployments can optionally run their own instance in the data plane, alongside the API and Brainstore, so Gateway requests are served from your own infrastructure.
How the Gateway works
Running the Gateway yourself keeps LLM traffic on your own infrastructure: requests egress directly from your VPC to providers, completions are cached (encrypted) in the data plane’s Redis, and internal features like Topics route through it too. When the Gateway is disabled, the data plane does not fall back to the Braintrust-hosted Gateway. The API service calls providers directly. On AWS, user-authored code such as custom scorers and tools runs in an isolated quarantine environment that cannot reach your main VPC directly. Terraform module v6.5.0 and later sends its LLM calls to the deployment’s own AI proxy, which forwards them to the Gateway onceenable_ai_gateway is set, so those calls stay in your AWS account.
Even with the Gateway enabled, two paths still leave your data plane:
- Built-in models are served by Braintrust, so requests for them reach the Braintrust-hosted Gateway at
gateway.braintrust.devrather than your own. Self-hosted organizations have built-in models disabled by default and opt in explicitly. - Operational telemetry (status, metrics, usage, and optional logs and traces) can still be sent to Braintrust’s control plane, the same as the rest of the data plane. See Telemetry and data retention. This is monitoring traffic, not LLM traffic.
Enable the Gateway
The Gateway is disabled by default. It reuses the Redis instance and Brainstore license key the data plane already provisions, so no additional secrets are required.Prerequisite - Before you deploy the Gateway service in your data plane, contact Braintrust to enable the Gateway for your organization. Until your organization is enabled, Gateway-dependent features stay hidden in the Braintrust UI, and requests continue to use the AI proxy.
- AWS
- GCP / Azure
Requires Terraform module v6.5.0 or later, which pins the Gateway image to the data plane version the module ships and keeps quarantine LLM calls in your account. The Gateway runs as an ECS Fargate service behind an internal load balancer.Three variables control the Gateway, so you can provision the infrastructure before routing traffic to it:
create_ai_gatewaycreates the private Gateway infrastructure (an internal ALB and the Gateway ECS service).enable_ai_gatewaysetsGATEWAY_URLon the APIHandler, AI Proxy, and API ECS service so internal data plane traffic routes through the Gateway. It requirescreate_ai_gateway.use_private_ai_gateway_originpoints the public CloudFront/v1/proxyendpoint at the private Gateway (a VPC origin on its internal load balancer). It requirescreate_ai_gateway.
-
Provision the infrastructure without changing traffic and apply:
-
Route internal traffic through the Gateway. Once the Gateway is healthy, set
enable_ai_gateway = trueand apply:Public/v1/proxytraffic still uses its existing origin. -
Cut over the public endpoint and apply:
CloudFront then routes
/v1/proxyand/v1/proxy/*to the private Gateway.
create_ai_gateway and enable_ai_gateway together in the first apply, then cut over the public endpoint with use_private_ai_gateway_origin once the Gateway is healthy.Configure task sizing and autoscaling with ai_gateway_cpu, ai_gateway_memory, ai_gateway_min_capacity, and ai_gateway_max_capacity. To authorize additional security groups to reach the internal ALB, set ai_gateway_authorized_security_groups. The API and Brainstore security groups are authorized automatically.The module pins the Gateway image, the same way it manages the API and Brainstore images, so upgrading the module is how you move to a newer Gateway. Leave ai_gateway_version_override unset unless Braintrust instructs you to pin a specific tag.