The Stripe key in the Slack thread is the incident
Someone needs access to the billing API. The fastest path is to paste the vendor key into Slack, drop it in a shared 1Password vault, or copy it into a twelfth Lambda’s environment. A week later you cannot list who still holds a working copy. A month later you cannot rotate it without a release. A year later it is in a laptop backup.
That is not sharing access. That is cloning a master key.
The alternative is two credentials, not one. The vendor secret stays in one place. Each human, app, or agent who needs to call the API gets a different key, bound to a proxy, bound to rules. Revoking a person is deleting their key. The vendor account does not move.
The objects are API credentials: what you issue vs what you hold. This post is the operating practice.
Members configure. Credentials call.
A member is a human in the management console, invited with a role (admin, and so on). They create proxies and credentials. They do not present a vendor key to Stripe when they click around the UI.
A proxy credential is a runtime identity. An agent, a CI job, a Power BI dataset, a teammate’s local script – each one that will hit the proxy gets its own. They never log into the console with it. They send it on the Authorization header to the regional data plane.
Do not issue a proxy credential because someone “should be able to see the logs.” Invite them as a member. Do not invite an agent as a member because it “needs the billing API.” Give it a proxy credential. The mix-up is how vendor secrets end up in chat.
One target, one proxy per caller
Create the vendor secret once as a target credential. Create a proxy credential per caller. Bind them on a proxy with proxyDefaultRuleEffect: "deny". Then add the allow rules that caller actually needs.
{
"credentialType": "target",
"credentialAuthType": "key",
"credentialName": "stripe-production",
"credentialRegion": "us-east-1",
"credentialSecret": {
"key": "Authorization",
"value": "sk_live_4c9a1f7b2e8d",
"addToHeader": true,
"prefix": "Bearer "
}
}Hand the caller the proxy value once, out of band, the same way you would hand a password – not in the ticket description, not in the group channel. The target value never leaves the data plane. GET on either record will not give it back. If the caller loses their proxy key, you create a replacement, point the proxy’s proxyProxyCredentialId at it, and delete the old one. The Stripe key does not change.
One target, many callers
A single target credential can power multiple proxies. The billing agent gets one proxy that allows GET /v1/charges and POST /v1/refunds. The analytics dashboard gets another proxy, same target, that only allows GET /v1/balance_transactions. Each proxy has its own proxy credential, its own rules, and its own meters. The callers do not know about each other. The vendor sees one set of keys.
This is the 1:N model: one vendor secret, N grants. Adding a caller is a POST for a proxy credential and a POST for a proxy. Removing a caller is deleting one proxy credential. Nothing else moves.
Scope with rules and meters, not trust
“This caller should only hit charges and refunds” is a rule, not a comment in a vault:
POST /clients/{clientId}/proxies/{proxyId}/rules
{
"effect": "allow",
"ruleActive": true,
"methods": ["GET", "POST"],
"path": {
"path": { "pattern": "^/v1/(charges|refunds)" },
"presence": "must_exist"
},
"priority": 10
}Leave proxyDefaultRuleEffect: "deny". Anything not explicitly allowed is blocked. Rules can match path, method, headers, query parameters, request body fields, and JWT claims – all with regex and {{variable}} interpolation from request context.
“This caller should not blow the monthly Stripe budget” is a meter:
POST /clients/{clientId}/proxies/{proxyId}/meters
{
"meterType": "request_count",
"meterActive": true,
"limits": {
"minute": 30,
"day": 2000
}
}A meter on the proxy is what stops a runaway loop from becoming a vendor invoice. For AI workloads, response_value meters can extract a numeric field from the upstream response (e.g. usage.total_tokens) and budget against that instead of raw request count.
For short-lived inbound auth, use jwtVerify
Static proxy keys work. For callers that already carry a JWT – agents issued tokens by Auth0, Okta, Cognito, SPIFFE/SPIRE, or Azure AD – jwtVerify proxy credentials verify the signature against a JWKS endpoint, check exp, iss, aud, and optional claims. No static shared secret. When the caller’s token rotates (every few minutes for SPIRE, every hour for most IdPs), the gateway verifies the new one automatically. The setup is in fine-grained authorization with OktaFGA.
Revoke the caller, not the vendor
When someone leaves, or an agent is retired, delete their proxy credential (or detach it from the proxy). Every other caller keeps working. The upstream key stays put.
When the vendor key itself must rotate – suspected leak of the target secret, vendor-mandated rotation – create a new target credential, switch proxyTargetCredentialId, confirm traffic, delete the old target. Callers do not redeploy. That rotation path is API token rotation; the agent-scale version of the same split is taming API key chaos for AI agents.
If you shared the vendor key the old way, rotation is the incident. You are hunting copies. The gateway model makes that hunt a row in a table.
When not to use this
If the person needs the vendor’s dashboard – refunds in Stripe’s UI, a Salesforce report the API does not expose – they need a vendor login with SSO, not a RequestRocket credential. The gateway shares API access. It does not replace the SaaS product.
If two jobs should be allowed the same paths, still issue two proxy credentials. Shared proxy keys recreate the Slack-thread problem one layer in. The cost of a second key is a POST. The cost of a shared key is not knowing who used it.
Short-lived, automatically expiring proxy keys are not what this control point issues today. If a job should die with the process, create a credential for that job and delete it when the job ends.
Next steps
Inventory who currently holds a copy of each vendor secret. For each secret, create one target credential. For each holder, create a proxy credential and a deny-by-default proxy (or attach them to an existing proxy with rules that match that job). Remove the vendor key from env files and chat history. The credentials guide is the field reference. RequestRocket is runtime access control for AI agents and apps calling APIs you don’t own – every call gets a least-privilege credential, a policy check, and an audit record, with no code changes.