Skip to main content

OAuth Client Setup

ZenSearch's per-user OAuth flows (Atlassian, GitHub, Microsoft 365, Google Workspace, Google Analytics, Zendesk) and the Slack workspace install flow need an OAuth client application registered with the upstream provider. For most providers, the platform admin pastes the resulting Client ID / Client Secret / Redirect URL into Admin → OAuth Clients, and the server hot-reloads them within 30 seconds.

This page documents the upstream-side steps for each provider.

Where to find the exact callback URL to register: open Admin → OAuth Clients, pick the provider, and fill in the Redirect URL field with your SERVER_PUBLIC_URL host. The amber Register this URL in {Provider}'s developer console box on that page derives the exact callback URL (the path differs per provider — e.g. Google Workspace uses /api/v1/integrations/google-workspace/... with a hyphen) and offers a one-click copy. Paste that value verbatim into the upstream console. Most providers validate the callback URL as an exact-match string — a trailing slash or path mismatch breaks the flow.

:::info Platform vs per-user vs per-team "Platform OAuth client" = the upstream app itself (one set of Client ID/Secret per deployment). Each individual user then runs through the OAuth flow in-product to grant ZenSearch access to their account; that per-user grant is stored in user_connector_credentials. Platform admins set up the OAuth client once; end users authorize each time they connect. :::

:::warning Slack is not yet wired through Admin → OAuth Clients Slack appears in the admin UI's provider list, but as of this writing the Slack handler still reads its OAuth client from environment variables (SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_OAUTH_REDIRECT_URL) — saving Slack credentials in Admin → OAuth Clients has no effect on the running flow. The Slack section below is included so the upstream-app registration steps live alongside the others; configure ZenSearch's side via env vars until the handler is migrated (tracked under per-user-OAuth Phase 2/3). :::


Atlassian (Jira / Confluence)

ZenSearch uses Atlassian's OAuth 2.0 (3LO) flow. One OAuth integration covers both Jira and Confluence.

Steps

  1. Open the Atlassian Developer Console and click Create → OAuth 2.0 integration.

  2. Name the app (e.g. ZenSearch) and accept the developer terms.

  3. On the Permissions page, scroll down to the API list and click Add next to each of:

    • Jira API — then click Configure and enable: read:jira-work, write:jira-work, read:jira-user.
    • Confluence API — then click Configure and enable: read:confluence-content.all, write:confluence-content, search:confluence, read:confluence-space.summary.

    Don't use the Add Marketplace or custom app modal at the top of the page — that's for enrolling third-party Marketplace apps and is unrelated to OAuth scopes.

  4. AuthorizationOAuth 2.0 (3LO) → set the Callback URL to the exact value shown in the admin page's amber Register this URL box. Save.

  5. Settings → copy the Client ID and Secret.

  6. Paste both into ZenSearch Admin → OAuth Clients → Atlassian (Jira / Confluence) along with your Redirect URL host. Save.

Required scopes

read:jira-work write:jira-work read:jira-user
read:confluence-content.all write:confluence-content search:confluence read:confluence-space.summary
offline_access

One OAuth grant covers both Jira and Confluence — users connect once and the agent's Jira tools (search, get, create, update, comment, transition issues) and Confluence tools (search pages, get/create/update pages) all use the same access token.

offline_access is appended automatically by ZenSearch's authorize URL builder and must be enabled in the Atlassian app for refresh tokens to work. Without it, users will be re-prompted to log in every hour.

Notes

  • A single OAuth integration can serve any number of Atlassian sites — users pick the site at authorize time. The platform admin does not need a separate client per Atlassian Cloud site.
  • Atlassian validates the callback URL as a full exact-match string (host AND path). Don't add a trailing slash.
  • If you previously connected when only the Jira scopes were requested, you'll need to disconnect (Settings → Integrations → Atlassian → Disconnect) and reconnect once. Atlassian doesn't widen scopes on existing tokens.

GitHub

ZenSearch uses GitHub's OAuth App flow (not the GitHub App flow — they are different products).

Steps

  1. Go to GitHub Developer settings → OAuth Apps (or your organization's Settings → Developer settings → OAuth Apps page if you want it owned by an org) and click New OAuth App.
  2. Fill in:
    • Application name: e.g. ZenSearch
    • Homepage URL: your SERVER_PUBLIC_URL
    • Authorization callback URL: the exact value shown in the admin page's Register this URL box.
  3. Click Register application.
  4. On the next screen, copy the Client ID and click Generate a new client secret, then copy the secret.
  5. Paste both into ZenSearch Admin → OAuth Clients → GitHub along with your Redirect URL host. Save.

Required scopes

repo

repo is the minimum that lets the agent's GitHub tools read repository metadata, code search, and issues across both public and private repos the user has access to. ZenSearch validates that GitHub returned the requested scopes and surfaces an error if the user (or org policy) drops them.

Notes

  • Choose between a personal OAuth App (lives under your GitHub user) or an organization OAuth App (Settings → Developer settings under your org). Org-owned apps are easier to hand off when team membership changes — recommended for production.
  • GitHub allows multiple authorization callback URLs only on GitHub Apps, not OAuth Apps. If you run separate dev/staging/prod ZenSearch deployments, register one OAuth App per deployment.

Slack

ZenSearch uses Slack's bot-token OAuth installation flow (one bot install per workspace).

Steps

  1. Go to Slack API → Your Apps and click Create New App → From scratch.

  2. Name the app (e.g. ZenSearch) and pick the development workspace. (You can later distribute it to other workspaces.)

  3. OAuth & PermissionsRedirect URLs → add the exact value shown in the admin page's Register this URL box. Save URLs.

  4. On the same page, scroll to Scopes → Bot Token Scopes and add each of:

    • app_mentions:read
    • chat:write
    • channels:history
    • groups:history
    • im:history
    • mpim:history
    • users:read
    • users:read.email
  5. Basic Information → copy the Client ID and Client Secret under App Credentials.

  6. Set the following on the core-api service and restart:

    • SLACK_CLIENT_ID — Client ID
    • SLACK_CLIENT_SECRET — Client Secret
    • SLACK_OAUTH_REDIRECT_URL — the callback URL you registered (must match exactly)

    Slack does not yet read from the platform OAuth Clients DB row (see the warning at the top of this page); env vars are the source of truth for now.

Required scopes

app_mentions:read chat:write channels:history groups:history
im:history mpim:history users:read users:read.email

Notes

  • For multi-workspace installs (cloud SaaS pattern), the same OAuth client serves any workspace that runs through the install flow — no need for one client per customer.
  • For single-workspace self-hosting, you can skip distribution and install directly into your one workspace.
  • The callback URL the admin page derives for Slack (/api/v1/integrations/slack/auth/callback) is the correct value to paste into both the Slack app's Redirect URLs list and your SLACK_OAUTH_REDIRECT_URL env var.

Microsoft 365

ZenSearch uses Azure AD (Entra ID) app registration with delegated Microsoft Graph permissions.

Steps

  1. Open the Azure Portal → Azure Active Directory → App registrations and click New registration.

  2. Fill in:

    • Name: e.g. ZenSearch
    • Supported account types: choose Multi-tenant if your users span multiple tenants, or Single-tenant for a locked-down deployment.
    • Redirect URI: select Web and paste the exact value shown in the admin page's Register this URL box.
  3. Click Register. On the resulting Overview page, copy:

    • Application (client) ID → Client ID in ZenSearch
    • Directory (tenant) ID → set M365_OAUTH_TENANT_ID (or common for multi-tenant)
  4. Certificates & secretsNew client secret → copy the Value immediately (it is only shown once).

  5. API permissionsAdd a permissionMicrosoft GraphDelegated permissions, then add the permissions matching the surfaces you want the agent to use (only request what you need):

    SurfaceRequired Graph permissions
    OneDriveFiles.ReadWrite.All
    MailMail.ReadWrite, Mail.Send
    CalendarCalendars.ReadWrite
    TeamsChannelMessage.Send, Channel.ReadBasic.All, Team.ReadBasic.All, Chat.ReadWrite
    MeetingsOnlineMeetings.Read, OnlineMeetingTranscript.Read.All, OnlineMeetingArtifact.Read.All, Chat.Read
    To-DoTasks.ReadWrite
    PeoplePeople.Read
    SharePointSites.ReadWrite.All

    ZenSearch always appends offline_access, openid, and User.Read automatically.

  6. If your tenant requires admin consent for any of the permissions, click Grant admin consent at the top of the API permissions page.

  7. Paste Client ID and Client Secret into ZenSearch Admin → OAuth Clients → Microsoft 365 along with your Redirect URL host. Set M365_OAUTH_TENANT_ID in .env if you need to pin a tenant.

Notes

  • Multi-tenant apps must be verified by Microsoft to appear in third-party tenant consent screens — for internal-only deployments, single-tenant is simpler and avoids verification.
  • The client secret has a max lifetime of 24 months; rotate before expiry.

Google Workspace

ZenSearch uses Google's OAuth 2.0 web application flow with delegated user consent.

Steps

  1. Open the Google Cloud Console → APIs & Services → Credentials for the project you want to own the client (create one if needed).
  2. OAuth consent screen → configure with your app name, support email, and developer contact. Add your domain to Authorized domains.
  3. Add the required Scopes to the consent screen (see table below). Save.
  4. CredentialsCreate credentials → OAuth client IDApplication type: Web application.
  5. Add the exact value shown in the admin page's Register this URL box to Authorized redirect URIs. Click Create.
  6. Copy the Client ID and Client Secret from the popup (the secret is also visible later in the credentials list).
  7. Paste both into ZenSearch Admin → OAuth Clients → Google Workspace along with your Redirect URL host. Save.
  8. On the Google Cloud project, enable each API your scopes need (e.g. Google Drive API, Gmail API, Calendar API, Sheets API, Docs API) under APIs & Services → Library.

Required scopes

SurfaceScope
Sheetshttps://www.googleapis.com/auth/spreadsheets
Docshttps://www.googleapis.com/auth/documents
Gmailhttps://www.googleapis.com/auth/gmail.modify
Calendarhttps://www.googleapis.com/auth/calendar.events
Drive (write to files the user creates)https://www.googleapis.com/auth/drive.file
Drive (read everything the user has)https://www.googleapis.com/auth/drive.readonly

ZenSearch always appends openid, https://www.googleapis.com/auth/userinfo.email, and https://www.googleapis.com/auth/userinfo.profile for identity resolution — these match what shows up on Google's consent screen and in your project's credential settings.

Notes

  • An OAuth consent screen marked External in Testing mode only allows users on your Test Users list — promote to In production to allow general workspace users (Google may require app verification depending on the scope set).
  • For a Workspace-only deployment, set the consent screen to Internal instead of External — no verification required, and only members of your Workspace organization can authorize.

Google Analytics

ZenSearch's Google Analytics 4 agent tools use a Google OAuth 2.0 client distinct from Google Workspace. The scope set is small (analytics.readonly + identity claims), but it MUST be a separate client and a separate Admin → OAuth Clients row — sharing the Workspace client would force every connected user to re-consent on the new scope at next refresh.

Steps

  1. Open the Google Cloud Console → APIs & Services → Credentials — same project as Workspace is fine; create a new client below.
  2. OAuth consent screen → make sure your app's user type, support email, and authorized domains are set (covered already if you've configured Workspace).
  3. Add the required Scopes to the consent screen (see table below). Save.
  4. CredentialsCreate credentials → OAuth client IDApplication type: Web application. Name it something like ZenSearch — Google Analytics.
  5. Add the exact value shown in the admin page's Register this URL box to Authorized redirect URIs. Click Create.
  6. Copy the Client ID and Client Secret from the popup.
  7. Paste both into ZenSearch Admin → OAuth Clients → Google Analytics along with your Redirect URL host. Save.
  8. On the Google Cloud project, enable Google Analytics Data API and Google Analytics Admin API under APIs & Services → Library. Both are required — the Data API powers run_ga4_report / run_ga4_realtime_report, the Admin API powers list_ga4_properties / list_ga4_metrics_dimensions.

Required scopes

SurfaceScope
GA4 reads (reports + property metadata + account summaries)https://www.googleapis.com/auth/analytics.readonly

ZenSearch always appends openid, https://www.googleapis.com/auth/userinfo.email, and https://www.googleapis.com/auth/userinfo.profile so the OAuth callback can stamp the user's email + Google subject ID into the credential row.

Notes

  • Per-property access: the OAuth user must have Viewer or higher on each GA4 property they want the agent to read. ZenSearch's list_ga4_properties returns only the properties the connected user has access to.
  • Quotas: GA Data API has project-wide quotas (default 200 queries per minute per property, 25k per day) plus per-user concurrency limits. Heavy agent usage on a small property can hit these; bump the quota in the Cloud Console if you see 429s.
  • Refresh tokens: ZenSearch requests access_type=offline + prompt=consent so Google always returns a refresh_token. If a user re-connects and the next refresh 400s, double-check the consent screen wasn't recently switched from "External (Testing)" to "External (In production)" without re-issuing consent.

PostHog

PostHog ships with two auth paths. Pick OAuth for Cloud users (zero per-user setup); pick (or also allow) Personal API Keys for self-hosted PostHog deployments or operators who'd rather not stand up an OAuth app.

OAuth (Cloud)

PostHog's OAuth surface is the first PKCE + Dynamic Client Registration flow in ZenSearch. Two important differences from the other providers on this page:

  • client_secret is OPTIONAL. PostHog's discovery doc supports token_endpoint_auth_method=none (PKCE-only public client). Leave the secret blank and ZenSearch's exchange request authenticates via PKCE alone. If your operator setup prefers a confidential client, you can also set a secret and PostHog will accept client_secret_post.
  • The client_id is either DCR-registered or manually pasted from PostHog's developer UI. Both work; DCR is the cleaner option if you control the deployment automation.

Steps

  1. Register the OAuth client. Two ways:
    • Manual: sign in to PostHog Cloud at https://us.posthog.com (or https://eu.posthog.com), go to My Account → OAuth applications, click Register new app. Fill in:

      • Name: e.g. ZenSearch
      • Redirect URI: the exact value from the admin page's Register this URL box (something like https://app.zensearch.example.com/api/v1/integrations/posthog/user/oauth/auth/callback).
      • Authorization grant type: Authorization code
      • Client type: Public (PKCE-only) is fine. Confidential works too — copy the secret.
    • Dynamic Client Registration (RFC 7591): one-off POST to PostHog's region-agnostic registration endpoint:

      curl -X POST https://oauth.posthog.com/oauth/register/ \
      -H 'Content-Type: application/json' \
      -d '{
      "client_name": "ZenSearch",
      "redirect_uris": ["https://app.zensearch.example.com/api/v1/integrations/posthog/user/oauth/auth/callback"],
      "token_endpoint_auth_method": "none",
      "grant_types": ["authorization_code", "refresh_token"]
      }'

      The response carries client_id. (Optionally pass "token_endpoint_auth_method": "client_secret_post" to get a client_secret back too.)

  2. Open ZenSearch Admin → OAuth Clients → PostHog.
  3. Paste the Client ID. Leave Client Secret blank if your client is public; otherwise paste the secret.
  4. Save.

Required scopes

ZenSearch's authorize URL builder requests these scopes (all read-only):

openid profile email
query:read dashboard:read insight:read
event_definition:read property_definition:read
project:read organization:read

openid + profile + email populate the user's display name + email in the credential row. project:read + organization:read are needed for project enumeration during/after consent.

Personal API Key (PAT) — required for self-hosted

End users with self-hosted PostHog deployments don't have OAuth available (unless their operator stood it up locally). They paste a Personal API Key directly into the Settings panel:

  1. In PostHog, Account Settings → Personal API Keys → Create personal API key.
  2. Grant the read scopes listed above.
  3. Copy the key (shown once at creation).
  4. In ZenSearch, Integrations → PostHog → Use a Personal API Key. Paste the key, the numeric Project ID, choose region (or self-hosted + base URL).

Self-hosted base URL allowlist (operator)

For self-hosted PostHog, ZenSearch SSRF-gates the base URL with an allowlist composed from two sources:

  • Operator env (read-only from the UI): POSTHOG_ALLOWED_HOSTS — comma-separated list of hostnames or URLs. Example: POSTHOG_ALLOWED_HOSTS=posthog.acme.com,analytics.example.io.
  • Per-team admin rows: team admins manage their team's allowlist in the PostHog Settings panel → Self-hosted PostHog allowlist section. GET / POST / DELETE to /api/v1/teams/:teamID/integrations/posthog/allowed-hosts (GET open to any team member; POST/DELETE require team admin).

A host passes the gate when it appears in EITHER source. The closed us.posthog.com / eu.posthog.com Cloud hosts are always allowed for Cloud regions and do NOT need an allowlist entry.

Notes

  • Refresh tokens: PostHog rotates refresh_token on every refresh under PKCE. ZenSearch persists the new token before writing the cache so a DB blip mid-refresh forces a clean reconnect rather than a silently-broken row.
  • Self-hosted + OAuth: this PR ships OAuth for Cloud only. Self-hosted PostHog OAuth (the operator stands up their own OAuth app on the self-hosted instance) is a follow-up — for now self-hosted users use PAT.
  • Validate-on-save: the credentials handler calls GET /api/projects/:id/event_definitions/?limit=1 against the user's chosen region before persisting. A typoed Project ID or stale region surfaces as a clean 400 in the Settings panel rather than a delayed AuthRequired bounce on the next agent run.

Zendesk

Zendesk OAuth clients are registered per Zendesk account (subdomain), not centrally. If your ZenSearch deployment serves users across multiple Zendesk accounts, register one OAuth client in each — they can share the same Client ID string but the secret will differ per account.

Steps

  1. Sign in to your Zendesk Admin Center: https://{subdomain}.zendesk.com/admin/apps-integrations/apis/zendesk-api.
  2. Open the OAuth Clients tab → Add OAuth client.
  3. Fill in:
    • Client Name: e.g. ZenSearch
    • Description: optional
    • Company: your company name
    • Unique Identifier: a stable string (this becomes part of the Client ID) — pick something like zensearch
    • Redirect URLs: add the exact value shown in the admin page's Register this URL box.
  4. Save. Zendesk shows the Secret once on the next screen — copy it immediately.
  5. The Client ID is the Unique Identifier you chose (sometimes shown as the value of unique_identifier in the resulting client row).
  6. Paste both into ZenSearch Admin → OAuth Clients → Zendesk along with your Redirect URL host. Save.

Required scopes

read write hc:read

read write covers tickets and users; hc:read covers Help Center articles. ZenSearch's authorize URL builder maps the tickets and help_center short names onto these.

Notes

  • The Zendesk subdomain is supplied by the end user when they connect (the connector form prompts for it), so the platform OAuth client doesn't need to be subdomain-aware — but the OAuth client must exist under the same subdomain the user types in.
  • If you operate a single Zendesk account (typical for self-hosted deployments), one OAuth client is all you need.

Troubleshooting

"redirect_uri mismatch" or 400 on callback

The most common cause is the path or trailing-slash mismatch. The Register this URL box on the admin page derives the exact callback URL from the Redirect URL field — copy that value verbatim into the upstream console, including the path. If you change SERVER_PUBLIC_URL, you must update the registration in every provider's console.

Hot reload didn't pick up the new credentials

The server caches resolved OAuth client rows for 30 seconds. Wait that long, or restart the core-api service to force an immediate reload. The admin UI's Source badge flips from Not configured / From env to Configured once the server sees the new row.

"OAuth client not configured" 400 in production

Check the Source badge for the provider on Admin → OAuth Clients. If it says Not configured, the row is missing or failed to decrypt. If it says Resolution error, the saved row exists but couldn't be decrypted with the current CREDENTIAL_ENCRYPTION_KEY — re-saving with the current key clears the error.

Rotating credentials

To rotate a Client Secret: generate a new secret in the upstream console, paste it into the admin form (Client ID stays the same), Save. Existing user-issued tokens remain valid until they expire and refresh — only future authorize flows use the new secret.