@keyward.dev/sdk
One client, two calls. gateway() proxies a provider API through Keyward so the agent never holds the key; get() mints a scoped, short-lived credential for native-mode providers. Credentials are cached client-side until they near expiry.
createClient(config)
Authenticate one of two ways: a service key (agentKey — the agent acts on its own authority; the simple MCP path) or an end-user token (token — the action is tied to the person who triggered it). Provide at least one; if both are set, the user token wins for that call.
client.gateway(provider, path, init?)
The primary call: send the SAME request you'd send the provider, but through Keyward. The broker maps (method, path) to a scope, checks the secret's allowed scopes and your policies, injects the real key server-side, forwards, audits, and returns the upstream Response verbatim. The agent never sees the key.
const res = await kw.gateway("openai", "/v1/chat/completions", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ model: "gpt-4o", messages: [{ role: "user", content: "hi" }] }),
});
const data = await res.json(); // the real OpenAI responseValid paths and their scopes per provider are in the Provider reference. A denial comes back as the broker's own JSON error (see Troubleshooting).
client.get(provider, scope, ttl?, opts?)
For native-mode providers (AWS STS today): returns a Credential — the short-lived credential the provider itself minted, plus token_type, provider, scope, expires_at, and the delegation pair. TTL defaults to 5 minutes. Denials throw KeywardError with the broker's status and reason. (For proxy-mode providers, prefer gateway() — a proxy credential can't be used directly against the upstream API.)
const cred = await kw.get("aws", "assume:deploy-role", "15m");
// cred.credential = short-lived STS credentials minted for exactly this scopeDelegation chains
A sub-agent acting under an orchestrator passes the orchestrator's credential instead of a user token. The broker verifies it, keeps the original human sub, and extends the RFC 8693 actor chain — nested, splice-resistant, depth-capped at 5.
// orchestrator got `cred` the normal way; hands it to the sub-agent
const sub = createClient({ controlPlaneUrl, agent: "sub-agent" });
const delegated = await sub.get("stripe", "charges:read", "5m", {
delegation: cred.credential,
});
// delegated.credential's chain: user → orchestrator → sub-agentAttested agents (secret zero)
If an agent is registered with an attestation_issuer, the broker fails closed unless the request carries a verifiable workload-identity token (GitHub Actions OIDC, Kubernetes service account, cloud instance identity). Pass a function to mint a fresh one per request:
const kw = createClient({
controlPlaneUrl, agent: "ci-bot",
agentKey: process.env.KEYWARD_AGENT_KEY,
agentAssertion: () => getGithubOidcToken(), // fresh per request
});Verifying issued credentials
Credentials are RS256 JWTs with a kid header. Resource servers verify them against the broker's public JWKS at GET /v1/jwks — no shared secret needed.