Skip to main content
After this guide, a tool reads a secret from the agent’s environment, calls your API with fetch, and returns a result the model can use, or a clear error when it can’t. The pattern fits any HTTP service; when the service is one of Lua’s integrations, connect it instead and skip the key handling. Verified against lua-cli 3.33.0. Before you begin
  • A tool registered in a skill on the agent (Add a tool to a skill).
  • The API’s base URL and a key for it.
1

Store the key for local runs

lua env sandbox writes the project’s .env file. lua test loads it before it runs your code, and lua chat -e sandbox uploads its values with each sandbox version, so sandbox turns read them from the platform.
The command rewrites .env from its key-value pairs, so don’t keep hand-written comments in it. Add .env to .gitignore; the scaffold doesn’t.
2

Call the API from execute

env('KEY') returns the value or undefined. Read what you need at the top, fail fast with a message the model can relay, and bound the wait with a timeout well under the tool’s budget.
src/skills/tools/GetOrderStatusTool.ts
A tool has 180 seconds of wall time, and the end user is waiting for all of it, so 10 seconds is a generous ceiling for one request (Platform limits). Turn an expected miss such as 404 into a result the model can explain; throw for everything else, because the error message is what the model sees. Return the fields the reply needs rather than the raw response: a result over 300,000 characters reaches the model truncated.
3

Run it locally

lua test runs execute on your machine with the values from .env and no model in the loop.
Output
When the API is unreachable, the run still exits 0 and prints { status: 'error', error: 'fetch failed' } after the stack trace; a thrown message appears as error the same way.
4

Store the key for production

Production code reads only the agent’s server-side environment; .env is read by lua test and uploaded with sandbox versions, and never reaches production (About environments). Set the values once; no push or deploy is needed, and a changed value applies on the next call.
lua env production --list prints the keys with values masked after the first four characters; the masking is display-only, and the interactive lua env production menu shows a variable’s full value to any credential holding knowledge:read. To rotate a key, set the replacement value and revoke the old one at the provider.
5

Release

Push uploads a skill version and changes nothing for end users; the agent version you create and promote is what goes live, and it is also the rollback path.
lua version create prints ✓ Created v<n> (staged). Run `lua version promote v<n>` to deploy.; <n> comes from that line, promote accepts <n> or v<n> and asks no confirmation, and in a script n=$(lua version list --limit 1 --json --ci | jq -r '.[0].version') reads it. lua deploy skill --set-version <v> serves that version from the agent’s next turn, but the next lua version promote resets the skill to the promoted agent version’s pin; push, create, and promote for a durable change. See Release an agent to production.
6

Verify

Ask the live agent and read the tool’s log. The reply quotes the status and carrier; the log’s Tool result line holds the object the tool returned, or the error it threw.
--name on lua logs is the name of the skill that holds the tool. A Tool result { status: 'error', … } with ORDERS_API_URL and ORDERS_API_KEY are not set means the production environment is missing a key.

Options you may need

Retry a failed request

Retry only requests that are safe to repeat: a GET, or a POST that carries an idempotency key your API honors. Retry once or twice with a short wait, only on a timeout or a 5xx status, and keep the total under the 180-second tool budget; a 4xx is your bug or the end user’s input and won’t change on retry. Never retry a write that has no idempotency key, because the first attempt may have succeeded.

Send a body

Pass method: 'POST', a Content-Type: application/json header, and body: JSON.stringify(payload). The same timeout and status checks apply.

If it isn’t working

lua test and sandbox chat read .env; production reads only the server-side map. Set the key with lua env production -k <KEY> -v <value> and send the message again.
The host didn’t answer: wrong ORDERS_API_URL, DNS, or a service that isn’t running. The cause is in the stack trace printed before the result. Deployed code can’t reach private-network addresses (execution contexts).
AbortSignal.timeout throws TimeoutError after the wait you set; the model reports the failure. Lower the timeout before adding a retry, and move long work to a job or workflow that reports back.

Next steps

Environments

What .env, the sandbox, and production each read.

Store and search data

Cache or record what your API returned.

lua env reference

Every flag, masking, and the environment aliases.

env reference

Reading variables and the template form for workflows.