Testing Agents Behind Authentication
Many production agents sit behind an API that requires authentication. A static credential (bearer / api_key / basic) is enough when the API accepts a fixed token, and an HTTP target reads that credential from your project’s secrets. An OAuth2 client-credentials flow (Auth0 “machine-to-machine” is the usual example) needs more, because a token must be exchanged first.
Start with the HTTP target below. When the credential must be exchanged, use a custom code agent. It is a few lines of Python that fetch the token and call your API with it. The credentials stay in your project’s encrypted secret store, never in the agent’s stored source.
Referencing project secrets from an HTTP target
An HTTP target reads a project secret as{{ secrets.NAME }}. You then do not type the API key into the target’s configuration, where all users who can open the target can read it. Store the credential in Settings → Secrets, then reference it by name.
References resolve in these fields:
A reference in the body template is sent as written. A reference to a secret name the project does not have also stays as written, and does not become an empty string. The request then fails as a missing secret, and not as an unauthenticated call.
Substitution runs when each request is built. A secret that you rotate applies on the next turn, and you do not change the target. The run removes resolved values from everything it shows back. An error from a rejected request gives the failure and shows
[redacted] where the credential was.
Auth fields typed in directly still work exactly as typed. A value with no {{ secrets.NAME }} in it is sent unchanged.
Code agents and workflow targets read the same secrets.NAME namespace.
A value that is not a credential, for example a tenant or a fixture id, goes in a run parameter. A run parameter is read as {{ params.NAME }}. You can change it when the run starts, and it is recorded on the run.
How it fits together
- Project secrets hold the client ID and client secret, encrypted at rest. They are exposed to your code agent’s Python as the injected
secretsnamespace. - The code agent exchanges the credentials for an access token and calls your protected API with it.
- A scenario drives the agent like a real user and judges the answers. The agent only passes if it got through the auth wall.
1. Store the credentials as project secrets
In Settings → Secrets, create the credentials and the endpoint coordinates. Keeping the endpoints in secrets too means the whole agent is configurable without touching its code or any API. CreateAUTH0_CLIENT_SECRET through the Settings → Secrets UI, not on a command line: a secret passed as a CLI argument lands in your shell history and the process listing, and can end up in CI logs. The non-sensitive coordinates are fine to create via the CLI:
UPPER_SNAKE_CASE (^[A-Z][A-Z0-9_]*$). Values are encrypted and never returned by any API.
2. Write the code agent
Create a code agent with a single inputmessage and a single declared output output: the declared output key must match the key the Python returns, or the run fails with missing_output. Use this Python, the reference implementation, exercised continuously against the real code-agent runtime:
- Entry point:
class Codewith__call__(no constructor arguments). secretsis injected into the module globals. It is not the Python stdlibsecretsmodule. Do notimport secrets; that would shadow the injected namespace.os.environis not populated in the sandbox.- Return every declared output key, or the run fails with
missing_output. - Available packages:
requests,httpx,pydantic,langwatch. - Budget: the token fetch plus your API call must finish inside the runner’s wall-clock limit (60s by default). Keep explicit timeouts on both requests.
- Failure behavior:
raise_for_status()puts the URL and status code in the error, never the request body, so a rejected credential fails loudly without exposing the secret.
3. Map the agent’s input for scenarios
Because everything else rides the secrets namespace, the agent has a single input, the conversation message, and needs exactly one mapping:message from the scenario’s input. That mapping is creatable in the agent editor today.
Keeping the endpoint coordinates in secrets is deliberate: the alternative, static
value mappings on extra inputs, currently cannot be created from the editor UI, only via the agents API. UI support for static value mappings is tracked in #6371.