--- title: 'stackbone.secrets' description: "Read operator-registered, encrypted secrets from your agent's own database at runtime." position: 7 --- # stackbone.secrets > Typed reads of encrypted secrets. Operators register API keys, tokens > and credentials in the Studio dashboard; your code reads them at > runtime through the ambient `stackbone` client without seeing the > ciphertext-at-rest. ## Mental model `stackbone.secrets` is the read side of your agent's credentials. It is **agent-local**: the operator registers API keys, tokens and credentials in the Studio dashboard, and Stackbone stores them **encrypted in your agent's own database**. `stackbone.secrets` reads them straight out of that database and decrypts them locally with the per-agent key the runtime injects as `STACKBONE_SECRET_KEY`. The ciphertext-at-rest never leaves your agent. You read secrets wherever you need a credential: from inside an [agent tool](/docs/sdk/agents/overview), or from a [durable workflow](/docs/sdk/workflows/overview) `'use step'`. The ambient `stackbone` client resolves these credentials lazily from the runtime-injected environment, so you import it and call: there is no `createClient()` and no per-invoke handle to wire up. ```ts import { stackbone } from '@stackbone/sdk'; const result = await stackbone.secrets.get('STRIPE_API_KEY'); ``` A secret read is a direct query against the agent's own database followed by a local decrypt: no control-plane round-trip and nothing to negotiate, so it is available from the first call in any tool or workflow step. The facade does not cache the values, so every call re-reads the database and secret rotations propagate on the next read. Because the read goes through the agent database, your agent must have its Postgres connection configured (the runtime injects `STACKBONE_POSTGRES_URL` for you). Every agent and workflow in your workspace reads this same surface, and so do your [Stackbone Connect](/docs/home/features/integrations) calls: they all read from the same install's database. ## Read a single secret Read one secret from inside an [agent tool](/docs/sdk/agents/overview). `get` returns a [`Result`](/docs/sdk/reference/overview) envelope: check `.error` before touching `.data`, and never log a decrypted value. ```ts import { tool } from '@langchain/core/tools'; import { stackbone, z } from '@stackbone/sdk'; const createCharge = tool( async ({ amountCents }: { amountCents: number }) => { const result = await stackbone.secrets.get('STRIPE_API_KEY'); if (result.error) throw new Error(result.error.code); // Pass the value straight to the downstream client. Don't log it. const stripe = createStripeClient(result.data); const charge = await stripe.charges.create({ amount: amountCents }); return JSON.stringify({ chargeId: charge.id }); }, { name: 'create_charge', description: 'Create a Stripe charge.', schema: z.object({ amountCents: z.number() }), }, ); ``` `get` rejects empty names with `secrets_invalid_request`. A name that has not been registered for this agent surfaces `secrets_not_found`. ## Read many at once `stackbone` is the same ambient client everywhere: `getMany` works identically from a tool's `execute()` and from a workflow step. ```ts const result = await stackbone.secrets.getMany([ 'STRIPE_API_KEY', 'STRIPE_WEBHOOK_SECRET', 'POSTMARK_TOKEN', ]); if (result.error) throw new Error(result.error.code); const stripeKey = result.data.STRIPE_API_KEY; // string | undefined ``` Names absent from the agent come back as **omissions** in the returned map: `getMany` never fails because one secret is missing. Callers that need all-or-nothing semantics should diff the keys themselves: ```ts const requested = ['A', 'B', 'C']; const present = Object.keys(result.data); const missing = requested.filter((k) => !present.includes(k)); if (missing.length > 0) throw new Error(`Missing secrets: ${missing.join(', ')}`); ``` A secret read works inside a durable workflow too. A [`'use step'`](https://workflow-sdk.dev/docs/how-it-works/understanding-directives) function runs once, persists its result and retries on failure. Wrap the credentialed work (fetching the secret and calling the third-party API) in a step so a transient failure replays cleanly: ```ts import { stackbone } from '@stackbone/sdk'; async function chargeCustomer(amountCents: number) { 'use step'; const result = await stackbone.secrets.get('STRIPE_API_KEY'); if (result.error) throw new Error(result.error.code); const stripe = createStripeClient(result.data); const charge = await stripe.charges.create({ amount: amountCents }); return { chargeId: charge.id }; } ``` ## Errors `stackbone.secrets` reads the agent database and decrypts locally, so its failures are database- and crypto-shaped rather than HTTP-shaped. | Code | When | | ------------------------- | ---------------------------------------------------------------------------------------------------------- | | `secrets_invalid_request` | Empty `name`, or an empty `names` array. | | `secrets_not_found` | `get(name)` and the agent has no secret with that name. | | `secrets_decrypt_failed` | The stored ciphertext could not be decrypted with the per-agent key. | | `secrets_not_configured` | `STACKBONE_SECRET_KEY` is missing, or the per-agent cipher could not be built from it. | | `secrets_unavailable` | The read against the agent database failed. | | `database_not_configured` | The agent has no Postgres connection configured (`STACKBONE_POSTGRES_URL` unset / agent database missing). | > **If the container your agent runs in restarts with a different encryption > key,** every secret stored under the previous key reads back as > `secrets_decrypt_failed`. Those values cannot be recovered, so register them > again in the dashboard. A row that exists but fails to decrypt makes a run > report the credential as missing. ## Where to go next - **[`stackbone.config`](/docs/sdk/platform/config)**: for non-secret per-agent config (feature flags, toggles, JSON snippets). - **[`stackbone.approval`](/docs/sdk/humans/approval)**: pair with `stackbone.secrets` to gate a paid API call on a human approval. - **[Agents](/docs/sdk/agents/overview)**: where agent tools live, the most common place to read a secret.