| name | auth-guide |
| description | Guide to setting up third-party authentication for a Notion Worker. Covers external-service API keys / personal access tokens and OAuth. Use when the worker needs credentials for a non-Notion API, not for Notion API tokens or `ntn login`. |
| user-invocable | false |
What this guide is for
This guide is for authentication against the third-party service your worker integrates with โ the place data is coming from or going to (GitHub, Stripe, Salesforce, Google, Slack, etc.).
Use it when the worker needs credentials for a non-Notion API. Do not use it for Notion API tokens, ntn login, or general Notion workspace setup.
Most workers will use one of two auth patterns from the upstream service:
- personal API key / personal access token
- OAuth
Decision framework
Before recommending anything, check the provider's current developer docs. Confirm whether it offers:
- personal API keys / personal access tokens
- OAuth
- neither
Always research the provider's current auth docs on the web before advising the user. Do not rely on memory for auth availability, setup steps, or settings locations.
Then choose mechanically:
- Service offers personal API keys / PATs? Recommend API key / PAT first. It is usually the simplest fit for an individual-scoped worker.
- Service is OAuth-only? Use OAuth.
- Both exist, but the worker should not depend on one person's credential or should be easy to re-auth if that person leaves? Recommend OAuth.
- Service has neither? See "When neither option is available" at the end of this guide.
State the recommendation in one sentence with the reason. Example: "Linear offers personal API keys, so use an API key; it's the simplest fit here."
Setup: API key
Pattern: store the credential in .env (or directly in the deployed worker's secrets), read it from process.env inside the capability's execute, push .env to the deployed worker before going live.
-
Look up the provider's current docs for creating a token. Link the user to the exact settings page when possible.
-
Have the user add the token to .env themselves (create the file if it doesn't exist), or tell them the equivalent ntn workers env set command. Tell them the variable name to use:
GITHUB_API_TOKEN=<paste your token here>
.env is automatically loaded for local execution (--local).
-
Read the token inside execute (this is the part you write):
const token = process.env.GITHUB_API_TOKEN ?? "";
const res = await fetch("https://api.github.com/user", {
headers: { Authorization: `Bearer ${token}` },
});
-
If auth seems broken, test the token outside the worker first against a simple authenticated endpoint from the provider docs. Example for GitHub:
curl -H "Authorization: Bearer $GITHUB_API_TOKEN" https://api.github.com/user
-
Test locally with ntn workers exec <capability> --local. Confirm auth works before deploying.
-
Push the secret to the deployed worker. Once the token is already in .env, run:
ntn workers env push
If the user prefers not to keep the token in .env, they can use the direct-set form instead:
ntn workers env set GITHUB_API_TOKEN=<paste token>
-
Tell the user how to rotate: revoke the old token at the service, generate a new one, update .env (or ntn workers env set directly), and re-push if needed.
Setup: OAuth
worker.oauth() declares an OAuth capability. The runtime handles the authorization redirect, token exchange, and refresh โ you call accessToken() inside execute to get a fresh token.
The user has to register an OAuth app with the provider, then plug the credentials in:
const myAuth = worker.oauth("myAuth", {
name: "my-provider",
authorizationEndpoint: "https://provider.example.com/oauth/authorize",
tokenEndpoint: "https://provider.example.com/oauth/token",
scope: "read write",
clientId: process.env.MY_OAUTH_CLIENT_ID ?? "",
clientSecret: process.env.MY_OAUTH_CLIENT_SECRET ?? "",
authorizationParams: { ... },
});
Setup steps:
-
Check the provider's current OAuth docs. Confirm the authorization endpoint, token endpoint, scopes, any extra authorization params, and how the provider wants redirect URLs configured.
-
Register an OAuth app with the provider. If the provider asks for a redirect URL up front and you do not have it yet, create the app shell first and come back to fill in the redirect URL after the first deploy.
-
Have the user add credentials to .env themselves, or tell them the equivalent ntn workers env set commands. Tell them which variable names to use:
MY_OAUTH_CLIENT_ID=<paste client id>
MY_OAUTH_CLIENT_SECRET=<paste client secret>
-
Add the worker.oauth() declaration to src/index.ts. Read clientId/clientSecret from process.env.
-
Create the worker (if not already created), push secrets, and deploy. The deployed worker reads clientSecret from environment variables during capability registration, so the secret must be present remotely before deploy. Have the user run these themselves:
ntn workers create --name <name> # if not already created
ntn workers env push # push .env to remote
# or, to set values directly without putting them in .env:
# ntn workers env set MY_OAUTH_CLIENT_SECRET=<paste secret>
ntn workers deploy
Important: any time the client ID or client secret changes, you must redeploy (ntn workers deploy) โ the OAuth capability binds these values at registration time, so updating env vars alone won't take effect.
-
Get the redirect URL and have the user add it to the provider's app settings. The redirect URL comes from the deployed worker. Get it with:
ntn workers oauth show-redirect-url
The user must paste this exact value into their OAuth app's "redirect URI" (or "authorized redirect URL", or "callback URL") setting before starting the OAuth flow. Always remind the user of this step โ OAuth will fail with a redirect mismatch error if it's missing or wrong.
-
Start the OAuth flow:
ntn workers oauth start <oauthCapabilityKey>
This opens the user's browser, walks them through the provider's consent screen, and stores the resulting tokens.
-
Use the token inside execute:
const token = await myAuth.accessToken();
const res = await fetch("https://provider.example.com/v1/things", {
headers: { Authorization: `Bearer ${token}` },
});
accessToken() returns a valid, refreshed access token. The runtime handles refresh automatically โ you don't need to track expiry yourself.
Local testing with OAuth
OAuth capabilities can be tested locally, but only after a one-time bootstrap โ the access token has to exist somewhere before accessToken() can read it. The flow:
-
Deploy the worker, configure the redirect URL, and complete the OAuth flow once (steps 5โ7 above).
-
Pull the deployed worker's env vars (which now include the OAuth access token) into local .env:
ntn workers env pull
-
Now ntn workers exec <key> --local works โ accessToken() reads the token from local .env.
Caveats:
- Access tokens expire. The deployed runtime auto-refreshes; your local
.env does not. When the local token goes stale, run ntn workers env pull again (or, if the refresh token has also expired, redo ntn workers oauth start <key> then env pull).
- Until that first deploy + OAuth completes, you can't
--local. Run npm run check for type validation, or mock accessToken() in a test file if you need to exercise the rest of the logic.
Common pitfalls
-
Hardcoded credentials in source. Tokens and secrets must come from process.env โ never inline them in src/index.ts. Even in personal repos, committed secrets get scraped.
-
Forgetting ntn workers env push. Local works, deploy fails with auth errors. Always push secrets after changing .env. The deployed worker doesn't see local .env.
-
Debugging worker code before testing the raw token. If API key auth is failing, hit a simple authenticated endpoint with curl first so you can separate bad credentials from worker bugs.
-
Pushing secrets after ntn workers deploy for OAuth. OAuth clientId is read from process.env during capability registration โ push secrets before deploy, or use the create โ env push โ deploy sequence.
-
Wrong redirect URL for OAuth. redirect_uri_mismatch is the #1 OAuth failure mode. Always run ntn workers oauth show-redirect-url and verify the user has set the exact URL at the provider.
-
Asking for too many OAuth scopes. Request the narrowest set that works. Scope creep makes the consent screen scary and slows OAuth review for production apps.
-
Not telling the user about manual rotation. API keys don't refresh themselves. Tell the user up front that they'll need to rotate, and how.
CLI reference
# Push .env secrets to the deployed worker (run after any .env change)
ntn workers env push
# Pull remote env vars into local .env (useful for OAuth: brings access tokens
# down so `ntn workers exec --local` can read them)
ntn workers env pull
# List remote env vars (without values)
ntn workers env list
# Set a single env var
ntn workers env set KEY=value
# OAuth: get the redirect URL to configure at the provider
ntn workers oauth show-redirect-url
# OAuth: start the authorization flow (opens browser)
ntn workers oauth start <oauthCapabilityKey>
# OAuth: inspect token state
ntn workers oauth token <oauthCapabilityKey>
When neither option is available
If the service offers neither an API key nor an OAuth flow, the honest first answer is often that the integration isn't viable on that service.
Before giving up, there are a few indirect paths worth considering.
-
OAuth into a related service that already has the data. Sometimes the data flows downstream into a place you can reach with proper auth โ a calendar provider, file storage, a shared workspace. Following the data to a sanctioned interface is preferable to forcing a connection at the original source.
-
Have the user export and upload. If the service offers a manual data export (CSV/JSON), the user can drop files somewhere the worker can read (S3, Drive, etc.) and the worker syncs from there. Higher-friction but unambiguously sanctioned.
-
Pull data out of the user's own email. If the service sends the user emails containing the data (digests, notifications, exports, receipts), OAuth into the user's own email account (Gmail, etc.) and parse those messages. The user owns the inbox, the service is sending them the data on purpose, and the email provider has a real OAuth API. Indirect but stable.
-
Use the service's own internal/frontend endpoints (the JSON routes its web app calls). Sometimes the only thing the service exposes is the API its own UI talks to โ you can authenticate as the logged-in user (session cookie, captured bearer token) and call those routes from the worker. Honest caveats: it's often flaky (the routes can change with any frontend release), it relies on credentials that probably weren't intended for programmatic use, and the user needs to confirm this doesn't violate the service's terms of service before doing it. Reasonable for a personal tool or hobby integration; not something to lean on for serious production use. Don't recommend it as a first choice โ but if the user goes this way knowingly, help them do it carefully (sane pacers, descriptive User-Agent, manual credential rotation, no rate-limit evasion).
Tip for discovery: ask the user to export a .har file from their browser's devtools (Network tab โ right-click โ "Save all as HAR with content"). HAR files capture every request/response the page made โ URLs, methods, headers, bodies โ which lets you see the exact endpoint shape without the user having to describe it.