Skip to content
Charming Docs
Esc
navigateopen⌘Jpreview
On this page

Secrets

Your app calls external APIs using a key the owner sets in the dashboard; the value never reaches your app's code or the model that built it.

What your app can connect to

Want your app to use another service, like a weather feed, Stripe, an LLM provider, or a notification service? The owner adds the key once, by name, in the app’s dashboard, and the app can call that service. Your agent never sees or handles the secret value itself.

Your app’s backend code references the key by name; Charming fills in the real value on its own infrastructure right before the request leaves, so the plaintext never sits in your app’s source, never shows up in a log, and never reaches the model that’s helping you build the app.

This works for any service that accepts a static API key. It doesn’t cover services that require you to sign in through their own login screen (OAuth).

Technical

The app owner sets, replaces, and deletes secret values from the Secrets section on the app’s dashboard page; an agent never sees or types a value, only the NAME.

Copy this prompt for your agent

Connect my Charming app to an external service. Read
https://charm.ing/docs/capabilities/secrets.md first. Ask me which
service if it isn't obvious from the app, or propose one. Then tell
me the exact secret NAME to set in the app's Secrets section; I'll
add the value there myself.
If you haven't built a Charming app before, read
https://charm.ing/docs/build-mcp.md (or build-http.md for the HTTP API)
for the app skeleton first.

How an agent performs this job

Your agent declares buildy:secrets/fetch@1.0 in the app’s manifest.capabilities.imports (claimed apps only; denied on an anonymous, unclaimed app) and lists the exact host the request needs in manifest.capabilities.fetchHosts; a sealed fetch requires an explicit allowlist, so an empty or missing one denies every request. Inside a route handler it calls env.fetch(url, init) with {{secret:NAME}} inside a header value. The agent doesn’t set or read the value: it tells the owner which NAME to use, and the owner enters the value from the app’s Secrets section in the dashboard. An owner (or anything holding their bld_user_* token) can also manage secrets directly over HTTP: GET, POST, PUT, and DELETE /app/{id}/secrets.

The contract

env.fetch only exists if the app declared buildy:secrets/fetch@1.0. Its shape:

env.fetch(url, init); // Promise<Response> - {{secret:NAME}} in a header value gets substituted
  • Substitution happens in header values only, never the URL or body, so a secret can’t leak into a log line or query string.
  • Secret names match ^[A-Z][A-Z0-9_]*$, the same charset a {{secret:NAME}} reference uses, so any settable name is always referenceable.
  • Each value is capped at 8 KiB (8192 bytes) of plaintext, checked before it’s encrypted for storage. Need a bigger value? Contact us.
  • env.fetch is HTTPS-only and enforces the same manifest.capabilities.fetchHosts allowlist and public-address (SSRF) guard as plain backend fetch: private, loopback, and cloud-metadata addresses stay blocked even on an allowlisted host.
  • Values are stored encrypted and are never returned by any endpoint, including to the owner; listing secrets returns names only.

Limits, access, and deletion

  • Setting values. Owner-only. From the dashboard, or over HTTP with the owner’s bld_user_* token: POST /app/{id}/secrets to create a new name (409 secret_already_exists if it’s already set; use PUT to replace it), PUT to replace an existing value, DELETE /app/{id}/secrets?name=NAME to remove one. App and render tokens are rejected on all of these; only a user token works.
  • Bad name. A name outside ^[A-Z][A-Z0-9_]*$ fails with invalid_secret_name.
  • Oversize value. A value over 8 KiB fails with secret_too_large.
  • Missing secret. If your code references a {{secret:NAME}} that isn’t set, env.fetch rejects. Catch that and handle the failure, or prompt the owner to add the secret, rather than sending the request with the placeholder still in it.
  • Deletion. Deleting a secret removes it immediately. Any code that still references its NAME fails on the next call until the owner sets it again or the code stops referencing it.

Was this page helpful?