Skip to main content

Use a Third-Party API Key from a Tool

This tutorial stores an API key for an outside service as a secret and uses it from a script tool, so the key never appears in the tool's code, its logs, or its responses. Along the way it covers services that want Basic auth, checking the signature on an inbound webhook, and rotating the key without touching the tool. About 15 minutes.

The examples use a made-up service, Acme, with its API at api.acme.com. Swap in your provider's hostname and auth scheme.

What you'll build​

  • A sealed secret acme-api-key that may only be sent to api.acme.com.
  • A script tool Create Acme Order that sends the key as a bearer token.
  • A variant that sends Basic auth.
  • A script tool that verifies the HMAC-SHA256 signature on an Acme webhook.

Before you start​

  • An API key from the provider. Most providers create these in their dashboard under API keys or Developers.
  • The exact hostname the provider's API lives on, for example api.stripe.com or api.github.com. Check the provider's API docs: it's the host in the URLs you call.

Step 1: Store the key​

In the builder:

  1. Open Secrets and click New Secret.
  2. Name it acme-api-key and add a description such as "Acme production API key, from the Acme dashboard".
  3. Paste the key into Value.
  4. Leave the mode as Sealed. You can get a new key from Acme any time, so nobody ever needs to read this one back.
  5. Under Allowed hosts, add api.acme.com.
  6. Click Create.

Or from the CLI, reading the key from a file so it doesn't land in your shell history:

autessa secrets create \
--name acme-api-key \
--description "Acme production API key" \
--mode SEALED \
--value @acme-key.txt \
--allowedHosts '["api.acme.com"]'

The response describes the secret, and never includes the value:

{
"secretId": "3f6c2a9e-8d41-4b7a-9c0e-5b2f1d7a4e10",
"name": "acme-api-key",
"mode": "SEALED",
"allowedHosts": ["api.acme.com"],
"boundToolIds": [],
"version": 1,
"myPermission": "MANAGE"
}

Delete acme-key.txt once the secret exists.

Step 2: Call the API from a script tool​

Create a script tool (see Tools) named Create Acme Order with two arguments, sku and quantity, and this script:

const key = secrets.ref("acme-api-key");

const res = JSON.parse(api.post(JSON.stringify({
endpoint: "https://api.acme.com/v1/orders",
headers: { "Authorization": "Bearer " + key },
body: {
sku: variables.get("sku"),
quantity: parseInt(variables.get("quantity"), 10)
}
})));

if (res.error) {
response.setResponse(JSON.stringify({ error: res.errorCode }));
} else if (res.statusCode >= 200 && res.statusCode < 300) {
const order = JSON.parse(res.body);
response.setResponse(JSON.stringify({ orderId: order.id }));
} else {
response.setResponse(JSON.stringify({ error: "Acme returned " + res.statusCode }));
}

What happens when it runs:

  • secrets.ref("acme-api-key") checks that you may use the secret and returns a handle, a placeholder string such as [[autessa-secret:…]]. It is not the key.
  • api.post replaces the handle with the real key inside the outgoing request, because the request is HTTPS and api.acme.com is on the secret's allowed hosts.
  • If Acme's response repeats the key anywhere, your script sees [redacted secret] instead.
  • console.log(key) would print the handle, never the key.

Run the tool once from the builder. Then open the secret and check its audit log: you'll see a USE event naming the tool and the host api.acme.com.

If you change the endpoint to any other host, the request isn't sent. api.post returns error: true with errorCode: "SECRET_HOST_NOT_ALLOWED" and statusCode: -1. To allow a second host, add it to the secret's allowed hosts.

Step 3: Let anyone run the tool​

Right now the tool can use the secret only when the person running it has Use access to the secret. That's fine while you test, but an app visitor, a webhook sender, or a scheduled job usually doesn't have that access.

To let the tool use the secret whoever runs it, bind the secret to the tool:

  1. Open the secret and click Edit.
  2. Under Bound tools, add Create Acme Order, and save.

Now only that tool can use acme-api-key, and it can do so for anyone allowed to run it. Limit who can edit the tool, because anyone who can change its script can use the key through it.

From the CLI:

autessa secrets update --secretId 3f6c2a9e-8d41-4b7a-9c0e-5b2f1d7a4e10 \
--boundToolIds '["<tool id>"]'

Step 4: Services that use Basic auth​

Some services want HTTP Basic auth: the header Authorization: Basic followed by the Base64 encoding of username:password. Ask for the secret with encoding: "basic" and the handle is filled in already encoded.

When the service gives you a username and a password or token, store the password or token as the secret and pass the username:

const auth = secrets.ref("acme-api-key", { encoding: "basic", username: "orders-bot" });

const res = JSON.parse(api.get(JSON.stringify({
endpoint: "https://api.acme.com/v1/orders?status=open",
headers: { "Authorization": "Basic " + auth }
})));

This sends the Base64 encoding of orders-bot:<the key>.

Many APIs, Stripe among them, take the API key as the username and an empty password. Leave out username for that:

const auth = secrets.ref("stripe-live", { encoding: "basic" });
// sends Base64 of "<the key>:"

Responses are checked for the Base64 form of the key too, so it's redacted if the service echoes the header back.

Step 5: Verify an inbound webhook signature​

Many services sign each webhook delivery with HMAC-SHA256 using a signing key they gave you, and send the signature as hex in a header. To check it, store the signing key as a secret and compare inside a script tool with secrets.verifyHmacSha256. The signing key never enters your script.

  1. Create a secret acme-webhook-signing with the signing key from Acme's webhook settings. Signing keys aren't sent anywhere, but a secret needs at least one allowed host, so use the provider's API host, api.acme.com.
  2. Create a script tool Check Acme Signature with two arguments: rawBody, the request body exactly as received, and signature, the hex signature from the header Acme names in its docs.
  3. Use this script:
const ok = secrets.verifyHmacSha256(
"acme-webhook-signing",
variables.get("rawBody"),
variables.get("signature")
);

response.setResponse(JSON.stringify({ valid: ok }));
  1. In your webhook handler, pass the raw body and the signature header to this tool, and act on the delivery only when valid is true.

The check uses a constant-time comparison, and each check adds a USE event to the secret's audit log with the host hmac.

Compute the signature over the body exactly as it arrived. If you parse the JSON and serialize it again, spacing and key order can change, and the signature won't match. Some providers also sign a timestamp plus the body, or send the signature with a prefix such as sha256=. Build the same message and strip the prefix as their docs describe before you call verifyHmacSha256.

Step 6: Rotate the key​

Rotate when a key may have leaked, when someone who had access to the provider account leaves, or on a regular schedule.

  1. Create a new key in the provider's dashboard. If the provider allows two active keys at once, leave the old one active for now.
  2. Open acme-api-key and click Rotate. Paste the new key and confirm. Or from the CLI:
autessa secrets rotate --secretId 3f6c2a9e-8d41-4b7a-9c0e-5b2f1d7a4e10 --value @new-acme-key.txt
  1. The secret's version goes from 1 to 2. Executions that start from now on send the new key. The tool needs no changes, because it refers to the secret by name.
  2. Run the tool once, and check the audit log for a USE event after the ROTATE event.
  3. Revoke the old key in the provider's dashboard.

Troubleshooting​

What you seeWhyFix
Script error SECRET_USE_DENIEDThe person or key running the tool has no Use access, the secret is bound to other tools, or the name is wrong.Share the secret with Use, bind it to this tool, or check the spelling. The secret's audit log shows a USE_DENIED event.
errorCode: "SECRET_HOST_NOT_ALLOWED"The request's host isn't on the allowed list, or the URL is http://.Use https:// and add the exact hostname to the secret's allowed hosts.
The provider returns 401The provider rejected the key.Check the auth scheme in the provider's docs (Bearer, Basic, or a custom header) and that the key is current. Rotate in a new key if needed.