Connections, keys and tokens
Connections#
A connection is a key or token plus the hosts it may be sent to. Apps never see the secret: wirl attaches it on the way out and forwards the request, so the call reaches the vendor with the credential in place while the app itself never holds it.
Declare what an app needs in wirl.json:
{
"connections": [
"hubspot",
{ "slug": "stripe", "hosts": ["api.stripe.com"], "kind": "http_bearer" }
]
}
A hosts list can only narrow a connection's own allowed hosts, never widen them. kind says what shape the credential takes: http_bearer (the default), http_header, basic, postgres, env, webhook_secret (checks incoming webhooks; no hosts), or none (no key at all; the hosts are the whole connection).
If the connection does not exist yet, deploying creates it, keyless, and hands back a link to the app's keys page:
npx wirl deploy
# Deployed crm-sync v1
# Needs a key: hubspot. Add it here, and the app starts working:
# https://app.wirl.dev/apps/acme/crm-sync/connections
The person opens that link and types the key into their own browser session; it goes straight into the vault. Until a key is set, calls on that connection are refused with a message saying the key is missing and pointing at the same link, rather than failing silently. A call to a host with no declared connection at all is refused outright.
A host that needs no key (a public API, a company service that checks nothing) is declared with "kind": "none" and its hosts; nothing is ever asked for, and the request goes out as the app sent it, to those hosts only:
{ "connections": [{ "slug": "openweather", "hosts": ["api.openweathermap.org"], "kind": "none" }] }
Locally under wirl dev the same rules apply: a host that resolves to a private address is refused there too.
The org Connections page#
/orgs/<org>/connections lists every connection an admin holds for the organisation: its name and kind, the hosts it may reach, who added it, and which apps are using it. A connection with no key yet shows a warning. Below the table, "What apps called" shows, by host, how many calls each app made through the gate in the last 30 days and how many were refused — a refused call never left wirl.
From that page an admin can revoke an app's grant on a connection (that app's calls to it start being refused at once; the key itself, and every other app's grant on it, are unaffected), grant it again later, or delete the connection outright. Deleting asks you to type the connection's slug to confirm, because there is no undo: every app using it loses access immediately. There is no organisation-level form for adding a connection; add one from an app's Connections page, or with wirl connections create.
Tokens#
A token carries one of two scopes. A browser session, and wirl login, always issue full.
deploycovers shipping: create an app on first deploy, deploy, roll back, pull source, list apps and versions and connections, set the description and accent, declare a connection the app needs, and read who it is signed in as.fullcovers everythingdeploydoes, plus anything that widens or hands over access, or touches credentials or people: sharing an app, changing who can open it, transferring or moving it, deleting it, creating or rotating or deleting a connection, deciding a grant, and adding or removing members or tokens.
Connecting an agent through MCP issues a deploy token unless the person ticks the box on the consent page: "Also let it manage sharing, connections, members and tokens (full access)".
The Connect an agent page (/settings/tokens) shows the MCP install command and lists every token with its scope and a revoke link.
If an agent holding a deploy token tries something only full can do, it gets back a 403 saying which action it cannot do and where to do it instead; the CLI and MCP surface that as a plain message telling the person to do it in the dashboard, or to reconnect with full access.