Credential Setup Guides
For agents: enroll or link?
Most of this page is written for the human doing dashboard setup. If you’re an agent deciding how to get a credential into TAP, start from the invariant:
The secret must never enter your context or the chat transcript.
That is the test. Any route that satisfies it is legal — judge a new one against the invariant rather than against a list. Three routes exist today:
| Situation | Path |
|---|---|
The secret already exists on this machine (.env, ~/.aws, an env var) | Enroll — pipe it directly |
| A human still has to generate or fetch it, and has a shell | Enroll command — they run it themselves |
| A human still has to generate or fetch it, and prefers a browser | Link — send them a setup link |
| Google / Microsoft — no pasteable secret exists, only a consent flow | Link (always) |
| Signing keys | Generate in-proxy (dashboard Signing Key template, or POST /app/users/{ext_id}/keys) — never enroll |
| The human isn’t at a terminal with the agent (web, phone) | Link |
The first row is settled by a fact you can check. The middle two are settled by your user’s preference, so ask rather than deciding for them: the link wins on a phone or when they’d rather not touch a shell, the command wins over SSH, on a headless box, or when a browser round-trip is just friction.
All three are the same endpoint, POST /agent/credentials — sending a value enrolls; omitting it returns both a prefilled create_url and an enroll_command. Full request/response shapes, required fields, and every error code: API Reference → POST /agent/credentials.
The one rule: never ask a human to paste a secret to you
Whichever door you take, the user must never type or paste a key into the chat. That is the single route none of the doors take, and between them they cover every case:
- The secret is already on the machine → enroll it by piping the value straight from its source. You are not being given anything new; you could already read that file.
- The secret is not on the machine yet, and they have a shell → hand them
enroll_command. They run it in their own terminal; the value goes from them to TAP without a stop in your context. - The secret is not on the machine yet, and they’d rather use a browser → send the link. The user enters it in the dashboard, over TLS, into an encrypted store. It never passes through you, your context, your logs, or the model provider’s.
If you catch yourself about to ask them to paste it here, you wanted one of the last two.
Handing over the enroll command safely
enroll_command comes back ready to paste. Pass it on verbatim — it is shaped so the secret stays out of the places a shell normally leaks one:
read -rsp 'Paste the secret for linear-2: ' TAP_SECRET; echo
jq -n --arg v "$TAP_SECRET" '{"allowed_hosts":["api.linear.app"],"auth_bindings":[{"header":"Authorization","format":"{value}"}],"name":"linear-2","value":$v}' \
| curl -sS -X POST "$TAP_PROXY_URL/agent/credentials" -H "X-TAP-Key: $TAP_AGENT_KEY" -H 'Content-Type: application/json' --data-binary @-
unset TAP_SECRETread -rs keeps the key off the command line (so out of ps) and out of shell history, jq --arg does the JSON escaping, and the body is piped rather than echoed. Improvising this by hand is how it goes wrong.
One trap worth stating plainly. Many agent harnesses let a user run a shell command from inside the conversation and echo both the command and its output back into it — Claude Code’s ! prefix, for one. Running enroll_command that way puts the secret straight into the transcript, which is the exact thing the invariant forbids, while looking like it worked. Tell your user to run it in a separate terminal.
enroll_command is null when there is no pasteable secret to run it with (Google/Microsoft consent flows, signing keys) or when you supplied no allowed_hosts — that field is required on the enroll branch, so a command without it would be dead on arrival. In both cases the link is the door.
Enrolling is not “the agent creates credentials.” It’s the agent enrolling a credential it already has access to — if an agent can cat .env, it already holds that key; enrolling moves it under TAP’s enforcement (host binding, per-request approval, audit) rather than handing the agent anything new.
Be precise about what enrolling does and doesn’t guarantee. It does not mean TAP hid the secret from you — you had filesystem access to it before you called, and TAP cannot change that. What you control is whether it ever entered your context: pipe the value from its source rather than reading it into a variable, printing it, or echoing it back. Do that and the key stays out of your transcript and out of anything downstream that reads your transcript. Read it aloud first and that property is gone permanently — rotation is the only fix. TAP cannot verify which happened; only you know, which is why the enroll response asks you directly.
The other two doors are stronger precisely because they make this unnecessary: there is nothing for you to be careful with, because you never hold the value at all. Prefer them whenever the secret isn’t already sitting on disk — and between them, prefer whichever your user says they prefer.
Enrollment does not weaken approval: an agent-enrolled credential requires human approval on every request by default, including reads. The human’s review moment is the credential’s first real use, not its creation.
Import from .env
If your keys already live in a .env file, skip the per-service setup: Credentials → Import from .env (also offered as the first step of onboarding).
- Paste the file (or pick it — it’s read locally in your browser, never uploaded as a file).
- TAP recognizes the secrets. ~25 well-known services are matched by env-var name and by the secret’s shape (
sk-ant-…→ Anthropic,ghp_…→ GitHub,xoxb-…→ Slack, …), so oddly-named vars still match.AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYand Twilio SID/token pairs collapse into single credentials. Config vars (DATABASE_URL,PORT, …) are skipped, and listed so you can see nothing was silently dropped. - Review. Each row shows an editable credential name and an allowed-hosts binding — the API host(s) the secret may be sent to, pre-filled for recognized services. This binding is enforced by the proxy: the secret can never be forwarded anywhere else.
- Unrecognized keys appear unchecked. On managed hosting, TAP asks an LLM to identify the service from the variable name alone (values are never sent) and pre-fills a host marked AI-suggested — verify. A wrong suggestion fails closed — the host binding means the worst case is a 403 until you correct it, never a leak. If no suggestion appears, type the host yourself: it’s the domain your code sends that key to.
- Import. Selected rows are created through the same API as the manual form; values are stored encrypted (in a hardware enclave on managed hosting). The description records provenance, e.g.
Imported from .env (OPENAI_API_KEY).
Once your agent uses TAP for these services, delete the raw keys from your .env — that’s the point.
The guides below are for services that need more than a pasted secret — OAuth flows, bundles, connectors.
Standard API Key
Select Standard API Key in the dashboard, give it a name, and paste the secret. TAP injects it into Authorization: Bearer by default.
Mercury
app.mercury.com/settings/tokens → Create API Token → Copy
Wise
wise.com/settings/api-tokens → Add new token → Full access → Copy
GitHub
github.com/settings/tokens → Generate new token (fine-grained) → Select repos and permissions → Copy
Slack
api.slack.com/apps → Create New App → OAuth & Permissions → Add scopes → Install to Workspace → Copy Bot User OAuth Token (xoxb-...)
Stripe
dashboard.stripe.com/apikeys → Reveal test/live secret key → Copy
OpenAI
platform.openai.com/api-keys → Create new secret key → Copy
Anthropic
console.anthropic.com/settings/keys → Create Key → Copy
Vercel
vercel.com/account/tokens → Create → Copy
Notion
notion.so/my-integrations → New integration → Copy Internal Integration Secret. Then share the relevant pages/databases with the integration.
SendGrid
app.sendgrid.com/settings/api_keys → Create API Key → Full Access or Restricted → Copy
Twilio
console.twilio.com → Account Info → Copy Auth Token. Use AccountSID:AuthToken as the secret value.
Cloudflare
dash.cloudflare.com/profile/api-tokens → Create Token → Copy
APIs with a non-standard auth header
There is nothing to configure. Add the credential the ordinary way — name, secret, allowed hosts — and let the agent put it where the API wants it:
curl -X POST "$TAP_PROXY_URL/forward" \
-H "X-TAP-Key: $TAP_API_KEY" \
-H "X-TAP-Target: https://api.linear.app/graphql" \
-H "X-TAP-Method: POST" \
-H "X-Linear-Token: <CREDENTIAL:linear>"Any header, any format — Authorization: Basic <CREDENTIAL:name>, X-Vendor-Token: <CREDENTIAL:name>, whatever the vendor documents. TAP substitutes the real secret after policy enforcement; the agent never sees it, and allowed_hosts still binds where it may be sent. Omit X-TAP-Credential when you do this.
This is the general answer, and it is why TAP asks a human for so little: the agent has read the vendor’s auth docs, so it already knows the header. Making a person look it up and re-type it into a dashboard field adds a step and a way to be wrong.
When you’d still set it in the dashboard: if you want plain X-TAP-Credential: <name> to work too — for well-known vendors TAP’s catalog fills this in silently, so anthropic, linear, exa, gemini, elevenlabs and friends already work both ways with nothing typed.
Multi-secret APIs (Datadog, and others)
Some APIs require two or more independent secrets in different headers. Enable the Multi-secret option and add one row per secret: the field name and the secret value. The agent then references each field where the API wants it:
DD-API-KEY: <CREDENTIAL:datadog.api_key>
DD-APPLICATION-KEY: <CREDENTIAL:datadog.app_key>For the vendors TAP’s catalog knows (Datadog, Plaid), the field→header mapping is filled in for you, so plain X-TAP-Credential: <name> works as well and the agent needn’t know the header names at all. For anything else, the placeholder form above is the normal way to use the credential — not a fallback for a broken one.
Datadog
Datadog needs an API key (DD-API-KEY) and an Application key (DD-APPLICATION-KEY).
-
Get your keys at app.datadoghq.com/organization-settings/api-keys and app.datadoghq.com/personal-settings/application-keys
-
In the TAP dashboard → Credentials → + Add Credential
-
Enable the Multi-secret option and add two rows:
Field name Secret value Target header api_keyyour Datadog API key DD-API-KEYapp_keyyour Datadog Application key DD-APPLICATION-KEY -
Save — TAP injects both headers automatically on every request
Datadog’s console shows a Key ID next to each key. TAP does not use it — paste only the key values.
Pick your Datadog region. A Datadog key is valid on exactly one site, and each site has its own API host. The credential form shows a region dropdown for any vendor that operates several — choose the host matching the URL you see in the Datadog console, and TAP binds the credential to just that one. Getting it wrong returns 403 with a perfectly valid key, which reads like a bad credential and is not. The same picker appears for Plaid (production / sandbox / development) and PostHog (US / EU), which have the same one-key-one-region rule.
Site API host US1 — app.datadoghq.comapi.datadoghq.comUS3 api.us3.datadoghq.comUS5 api.us5.datadoghq.comAP1 api.ap1.datadoghq.comEU1 — app.datadoghq.euapi.datadoghq.euUS1-FED api.ddog-gov.comThe Holonym Foundation org is on US5. Separate Datadog accounts (Passport has its own) may be on a different site, so check per account rather than assuming.
Because Datadog is in TAP’s catalog, its two headers are wired for you and plain X-TAP-Credential: datadog works. For a multi-secret API TAP doesn’t know, skip the wiring and use <CREDENTIAL:name.field> placeholders (see Proxy API) — same result, nothing to configure.
AWS (SigV4)
AWS APIs use SigV4 signing. TAP signs every request automatically — no pre-signing needed.
- console.aws.amazon.com/iam → Users → Select user → Security credentials → Create access key → Copy Access Key ID and Secret Access Key
- In the TAP dashboard → + Add Credential → select AWS
- Enter your Access Key ID and Secret Access Key
- Region and Service are auto-detected from the target URL. You can override them if needed.
- Save — TAP signs requests with SigV4 automatically
OAuth 2.0 Client Credentials
For machine-to-machine APIs that use the OAuth 2.0 client credentials grant (Azure Resource Manager, Salesforce, Box, Okta, and others). TAP exchanges the credentials for a Bearer token on every request — the agent never sees the secret or the token.
Credential JSON
In the TAP dashboard → + Add Credential → pick the Azure / Entra tile (under Google & Microsoft — it asks for tenant ID, client ID and client secret and builds the token URL for you) or the OAuth2 client credentials tile (under More, for Salesforce, Okta, Box and any other provider). The stored fields are:
| Field | Required | Description |
|---|---|---|
client_id | Yes | The application/client ID |
client_secret | Yes | The client secret |
token_url | Yes | The token endpoint URL |
scope | Sometimes | Space-separated scopes. RFC 6749 makes this optional, but Microsoft (Entra ID) rejects an app-only token request that carries no scope — for Graph it is https://graph.microsoft.com/.default, for Azure Resource Manager https://management.azure.com/.default. The dashboard requires it whenever the token URL is a Microsoft login endpoint. |
Azure Resource Manager
- portal.azure.com → Azure Active Directory → App registrations → New registration → name it → Register
- Copy the Application (client) ID and Directory (tenant) ID
- Certificates & secrets → New client secret → copy the value immediately
- Subscriptions → your subscription → Access control (IAM) → Add role assignment → assign Reader (or higher) to the app
- In TAP, enter:
token_url:https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/tokenscope:https://management.azure.com/.default
For Microsoft Graph app-only access (no signed-in user), the same registration works — grant application permissions under API permissions, click Grant admin consent, and use scope: https://graph.microsoft.com/.default. If you want Graph access as a signed-in user instead, use the Microsoft credential template, which runs a consent flow and refreshes delegated tokens.
Salesforce
- Setup → App Manager → New Connected App → enable OAuth → add scopes → Save
- Copy Consumer Key (
client_id) and Consumer Secret (client_secret) token_url:https://login.salesforce.com/services/oauth2/token
Google (OAuth 2.0)
For Gmail, Calendar, Drive, Sheets, and any Google API — including the Admin SDK (Directory, Reports) for Workspace administration and the full Google Cloud Platform surface (Service Usage, IAM, Compute, …).
TAP handles the OAuth 2.0 flow for you.
- In the TAP dashboard, click + Add Credential and select Google
- Check the permissions this credential should have. Gmail, Calendar, Drive, and Sheets are pre-selected; Contacts, Tasks, Workspace Admin (manage users, groups, and org units), Admin Reports (read-only audit/usage logs), and Google Cloud (full GCP API access) are opt-in
- Click Connect Google Account
- Sign in with the Google account you want the agent to access and grant permission
TAP stores the refresh token and handles token refresh automatically. Only the permissions you checked appear on Google’s consent screen — and the credential is permanently limited to them, so create separate credentials for separate jobs.
Keep all permissions checked on Google’s consent screen. Google lets you uncheck individual permissions during consent, but a credential missing some of its requested permissions would fail in confusing ways later — so TAP rejects partial grants and asks you to retry. If you don’t want a permission, deselect it in the TAP dashboard instead, before connecting.
Workspace Admin scopes only work if the connecting Google account holds an admin role, and they can manage your entire Workspace. Put them in a dedicated credential (separate from day-to-day Gmail/Drive) and consider requiring passkey approval in its policy. The same goes for Google Cloud — it grants whatever GCP IAM lets the connecting account do, project-wide.
Advanced: You can also paste a refresh token manually using the collapsible option in the credential form.
Google Cloud (Service Account)
For automation against Google Cloud: CI jobs, scheduled agents, infrastructure queries — anything that has to run without a human present.
Use this rather than the Google OAuth credential above whenever a human is not in the loop. A Google OAuth credential is tied to a person, and Google gates sensitive operations (enabling an API, changing IAM) behind reauthorization that expires within minutes. An agent will hit that wall and stop. A service account has no such flow: it authenticates with a signed assertion and keeps working.
Credential JSON
In the TAP dashboard → + Add Credential, set the connector to sidecar and paste the service account key JSON exactly as Google issues it:
{
"type": "service_account",
"project_id": "your-project",
"private_key_id": "…",
"private_key": "-----BEGIN PRIVATE KEY-----\n…\n-----END PRIVATE KEY-----\n",
"client_email": "your-sa@your-project.iam.gserviceaccount.com",
"token_uri": "https://oauth2.googleapis.com/token"
}TAP signs a short-lived JWT assertion with the key and exchanges it for an access token on each request. The key never leaves TAP and the agent never sees a token.
type must be exactly service_account — that field is how TAP tells this apart from a Google OAuth bundle.
Narrowing scope
By default the credential requests https://www.googleapis.com/auth/cloud-platform, which is everything the service account’s IAM roles allow. To narrow it, add a non-standard scopes field (space-delimited) to the JSON before pasting:
"scopes": "https://www.googleapis.com/auth/bigquery.readonly"Grant the service account the narrowest IAM roles that do the job, and bind the credential’s allowed_hosts to *.googleapis.com so it cannot be pointed elsewhere. Note that billing roles are granted on the billing account, which is a separate resource from the project.
Creating the key may be blocked. Many organizations set the org policy
constraints/iam.disableServiceAccountKeyCreation, which prevents downloading a key at all. If so, Workload Identity Federation is the alternative — it has no long-lived key to leak, and is tracked separately.
Failures here are configuration errors, not reauthorization ones. If a request fails with
gcp_service_account_mint_failed, check that the key is still active, that the service account holds the required IAM role, and that the target API is enabled on the project. There is no “reconnect” step for a service account.
X / Twitter
Use one TAP credential for X. It can hold:
- an X app Bearer Token for app-auth reads such as recent search and recent counts
- optional OAuth 1.0a account fields for user-context actions such as posting as an account
TAP chooses the auth mode per request. By default, read-like requests use the Bearer Token when present, while writes use OAuth 1.0a when present. If a read-like request gets a 401/403 from X and the same credential has OAuth 1.0a fields, TAP retries once with OAuth 1.0a. Agents should only use X-TAP-Auth-Mode: bearer or X-TAP-Auth-Mode: oauth1 as an escape hatch for a specific route.
The dashboard preset binds the credential to api.x.com so an agent cannot send it to an arbitrary host.
Step 1: Create a Twitter Developer App
- Go to the Twitter Developer Portal
- Create a new Project and App (or use an existing one)
- Add X API credits if the endpoint requires pay-per-usage access
- Copy the app Bearer Token from the app’s Keys and Tokens tab
- Optional: under User authentication settings, enable OAuth 1.0a with the access level your agent needs (Read, Read+Write, or Read+Write+DM)
Step 2: Get your credentials
For search/counts, collect:
- Bearer Token
For posting/user-context actions, also collect:
- Consumer Key (API Key)
- Consumer Secret (API Key Secret)
- Access Token
- Access Token Secret
If you change your app’s permissions, regenerate the access token.
Step 3: Add to TAP
- In the TAP dashboard, click + Add Credential and select X / Twitter
- Paste the Bearer Token and/or all four OAuth 1.0a values
- Click Create
Example recent search through TAP:
curl -X POST "$TAP_PROXY_URL/forward" \
-H "X-TAP-Key: $TAP_API_KEY" \
-H "X-TAP-Credential: x" \
-H "X-TAP-Target: https://api.x.com/2/tweets/search/recent?query=python%20lang%3Aen%20-is%3Aretweet&tweet.fields=created_at,public_metrics,author_id" \
-H "X-TAP-Method: GET"Recent counts also uses the same credential:
curl -X POST "$TAP_PROXY_URL/forward" \
-H "X-TAP-Key: $TAP_API_KEY" \
-H "X-TAP-Credential: x" \
-H "X-TAP-Target: https://api.x.com/2/tweets/counts/recent?query=python%20lang%3Aen%20-is%3Aretweet" \
-H "X-TAP-Method: GET"Telegram (Personal Account)
Lets agents read and send messages as a real Telegram user. The dashboard walks you through the setup — no scripts needed.
Step 1: Get API credentials
- Go to my.telegram.org/apps and log in with your phone number
- Create a new application (or use an existing one)
- Note the API ID (a number) and API Hash (a hex string)
Step 2: Open the Telegram wizard in TAP
In the TAP dashboard, click + Add Credential and select Telegram. A dedicated 3-step modal opens:
- Credentials — enter a credential name, optional description, your API ID and API Hash, then click Next
- Phone — enter the phone number registered to your Telegram account and click Send code; Telegram will send a code to your Telegram app
- Verify — enter the verification code. If your account has Two-Factor Authentication enabled, a password field appears — enter your 2FA password and click Connect
TAP generates and stores the session automatically.