Getting Client API Keys to Your Coding Agent Without Leaving Them in Chat
Have the client paste the key into a BriefGate portal as a secret item, then pull it with get_intake_results — the plaintext is handed to your agent exactly once and never sits in chat history, a Slack thread, or an email you'd have to remember to delete.
The problem with the obvious channels
A coding agent building a client's site eventually needs something only the client has: a Stripe secret key, a Mapbox token, a Google Maps API key, the credentials for a third-party service the agent is wiring up. The fastest way to get it is to ask — in the chat, over Slack, in an email reply. All three leave the same trace: a plaintext credential sitting permanently in a transcript, a channel history, or an inbox, searchable by anyone who later gets access to that chat, that Slack workspace, or that mailbox. None of them were built to hold a secret; they just happen to be wherever the conversation was already happening.
It's also not a question an agent can route around on its own. MCP's elicitation/create request can ask whoever is driving the agent a quick question mid-session, but the spec draws a hard line around exactly this case: "Servers MUST NOT use elicitation to request sensitive information." And even setting that rule aside, elicitation only reaches the person on the other end of the current connection — not a client who has never opened an MCP client and isn't part of the session at all. Async human input for coding agents goes through why that gap exists in the protocol itself; this page is about the practical workaround.
The workflow
Declare the key as a secret item when you create the intake:
{
"project_name": "Bella Napoli — Website",
"client": {"email": "owner@bellanapoli.com", "name": "Maria"},
"items": [
{
"key": "maps_api_key",
"type": "secret",
"label": "Google Maps API key",
"help": "From your Google Cloud Console — used to show your location on the new site. Encrypted, and only your developer's agent will see it, once.",
"required": true
}
]
}The client opens the portal link BriefGate emails them and sees a masked password field with a lock icon, not a text box that looks interchangeable with every other field on the form. There's no show-all and no copy-to-clipboard trail — once they submit, the value is gone from their side of the screen. What happens to it next is encryption, not just UI: the value travels over HTTPS and is sealed with a libsodium sealed box on arrival at the server, before anything touches the database. How client credentials are stored covers that mechanism end to end, including the one thing worth being precise about — it's server-side encryption, not end-to-end or zero-knowledge.
Once the client has submitted it, call get_intake_results:
{
"intake_id": "in_8f3kQmR2"
}{
"results": {
"maps_api_key": {
"value": "AIzaSyD-9tSrke72PouQMnMX-a7eZSW0jkFMBWQ",
"one_time": true,
"first_reveal": true,
"expires_at": "2026-11-04T10:22:00Z"
}
},
"meta": {
"maps_api_key": { "type": "secret", "status": "approved", "submitted_at": "2026-10-05T09:12:00Z" }
}
}That value field is only there because this is the first successful read. Call get_intake_results again — even seconds later, even to double-check — and value is gone; a companion meta entry reports secret_unavailable: true instead. BriefGate does give a short safety margin for the realistic failure mode, a response that never arrived: the same API key calling again within 5 minutes of the first reveal gets the same value back, so a dropped connection doesn't cost you the key. Past that window, the only way to see it is a fresh one — asking the client to submit it again.
Every reveal — whether it's your agent's API call or the account owner clicking Reveal in the dashboard — is written to an append-only audit trail (who, their IP, and when) that the owner can read from the dashboard. And if the client doesn't have the credential yet, mark the item required: false when you declare it; the intake still completes without it instead of blocking on a value that doesn't exist.
Where it goes after your agent has it
BriefGate's one-time reveal solves the "get it out of chat" problem; what you do with the value afterward is on you, the same as any other credential an agent handles. The honest, boring answer is the one you'd give for any secret: write it to a local .env file that's in .gitignore and never gets committed, or into whatever secret manager the project already uses, and reference it from code as process.env.MAPS_API_KEY rather than inlining the literal string anywhere a diff or a log might catch it. Don't echo it back into the chat to confirm you received it — the whole point of the one-time reveal is that the value existed in exactly one place after the client typed it, and printing it to a terminal the client (or a screen-sharing viewer) might see undoes that.
If the agent is running unattended — a CI job, a scheduled task — using an API key instead of a logged-in session, note that reading a secret item needs the secrets:read scope (or admin) on that key specifically; intakes:read alone isn't enough, and an attempt without it comes back with the item simply omitted and a meta entry explaining why. That's worth setting up deliberately rather than reaching for admin out of convenience, since secrets:read is exactly the privilege the task needs and nothing more.
What this doesn't cover
Secret items are only available on BriefGate's Solo, Studio, and Agency plans — the Free plan returns plan_required if you try to add one, so this workflow assumes the account has been upgraded. And a secret auto-expires 30 days after submission if nobody ever reveals it, which is a reasonable backstop but not a substitute for reading it promptly once the client has submitted.
This also isn't the only way a client might hand over an API key — a password manager's shared entry is a fine alternative when both sides already use one, and Getting client logins securely weighs that option against BriefGate's portal. What a secret item buys you specifically is a workflow your agent can drive end to end, from define_intake to get_intake_results, without a human relaying the value by hand in either direction.
Related
- How client credentials are stored — the encryption mechanism behind every
secretitem, and what it doesn't claim - Async human input for coding agents — why MCP elicitation can't reach a client, and what the alternative looks like
- Secrets vault — full reference: plans, scopes, reveal windows, expiry
- Item types — the
secrettype's fields, alongside the other eleven - MCP tools —
get_intake_resultsanddefine_intakein full