Marketplace OAuth installs
Threada supports a shared OAuth install path for the first runtime-bound marketplace integrations: GitHub, Atlassian/Jira, Notion, Asana, monday.com, and Intercom, plus contact-scoped HubSpot and Salesforce installs and ticket-scoped Zendesk installs.
This is a runtime integration surface, not a marketplace approval signal. A provider is live only after the provider app/listing is approved and the provider-owned install path has been tested in production or a reviewer-safe environment.
Architecture
- An authenticated Studio/admin user starts install from the pack connector
readiness panel or directly at
/api/v1/integrations/{provider}/oauth/start. public-bffencrypts and authenticates a stateless OAuthstatepayload containing provider, tenant, installer user, return URL, optional provider resource host, optional provider resource context, nonce, and expiry.- The provider redirects to
/api/v1/integrations/{provider}/oauth/callback. public-bffexchanges the code, discovers the provider-owned install resource when the provider exposes one, writes the OAuth credential JSON to Secret Manager, and stores only the generatedsecret_refin runtime automation.runtime-automationexecutes actions through the runtime adapter and reads only the generated Secret Manager credential for the tenant/provider.
public-bff owns the external OAuth edge. Runtime automation owns integration
state and execution. Provider tokens are never stored in MongoDB, Pulumi config,
logs, or evidence artifacts.
Provider App Setup
Create one provider app/integration per environment and register these callback URLs:
- GitHub:
https://api.<env>.threada.ai/api/v1/integrations/github/oauth/callback - Atlassian/Jira:
https://api.<env>.threada.ai/api/v1/integrations/atlassian/oauth/callback - Notion:
https://api.<env>.threada.ai/api/v1/integrations/notion/oauth/callback - Asana:
https://api.<env>.threada.ai/api/v1/integrations/asana/oauth/callback - monday.com:
https://api.<env>.threada.ai/api/v1/integrations/monday/oauth/callback - Intercom:
https://api.<env>.threada.ai/api/v1/integrations/intercom/oauth/callback - HubSpot:
https://api.<env>.threada.ai/api/v1/integrations/hubspot/oauth/callback - Salesforce:
https://api.<env>.threada.ai/api/v1/integrations/salesforce/oauth/callback - Zendesk:
https://api.<env>.threada.ai/api/v1/integrations/zendesk/oauth/callback
Use https://api.threada.ai/... for production.
Provider-owned marketplace install buttons and reviewer notes should point at Studio public install aliases, not directly at BFF OAuth routes. Studio resolves the authenticated tenant, requires exactly one enabled Studio-compatible pack, and sends the installer through the same connector readiness return path used by the in-app connector panel:
- Slack:
https://studio.threada.ai/integrations/slack/install - GitHub:
https://studio.threada.ai/integrations/github/install - Atlassian/Jira:
https://studio.threada.ai/integrations/atlassian/install - Notion:
https://studio.threada.ai/integrations/notion/install - Asana:
https://studio.threada.ai/integrations/asana/install - monday.com:
https://studio.threada.ai/integrations/monday/install - Intercom:
https://studio.threada.ai/integrations/intercom/install - HubSpot:
https://studio.threada.ai/integrations/hubspot/install - Salesforce:
https://studio.threada.ai/integrations/salesforce/install - Zendesk:
https://studio.threada.ai/integrations/zendesk/install
These aliases are executable install entrypoints only for providers with a BFF
OAuth implementation and a Studio alias listed above. Zendesk installs require
the target Zendesk account subdomain; Studio appends it as zendesk_subdomain
when the installer starts OAuth from the connector readiness panel, and
provider-owned links may include the same query parameter. monday.com installs
require a reviewer/customer board mapping before OAuth starts; Studio appends
board_id or monday_board_id and may append group_id or monday_group_id
when the installer selects a board/group from the connector readiness panel.
Provider-owned reviewer links may include the same query parameters. Do not create OAuth
aliases for Zapier, Teams, Google Workspace, Freshworks, Okta, or cloud
marketplaces until the corresponding provider-approved OAuth or acquisition flow
exists in runtime code. Google Workspace, Zapier, Teams, Datadog, PagerDuty,
Discord, Freshdesk, and Okta use the separate marketplace directory setup alias.
Slack keeps its OAuth setup alias for command installs and also exposes the
authenticated admin credential install route for governed post-message setup.
Datadog, PagerDuty, Discord, Freshdesk, Okta, and Slack expose that route at
/api/v1/integrations/{provider}/directory-install.
Those providers are documented in
Marketplace follow-on setup and support;
Google Workspace is documented in
Google Workspace setup and support; Zapier is
documented in Zapier setup and support; Teams is documented
in Microsoft Teams setup and support; Freshdesk is
documented in
Freshworks Marketplace for Freshdesk.
Secret Manager
Seed these Secret Manager values per environment before enabling the matching marketplace install routes:
marketplace-oauth-state-secret-<env>github-oauth-client-id-<env>andgithub-oauth-client-secret-<env>atlassian-oauth-client-id-<env>andatlassian-oauth-client-secret-<env>notion-oauth-client-id-<env>andnotion-oauth-client-secret-<env>asana-oauth-client-id-<env>andasana-oauth-client-secret-<env>monday-oauth-client-id-<env>andmonday-oauth-client-secret-<env>intercom-oauth-client-id-<env>andintercom-oauth-client-secret-<env>hubspot-oauth-client-id-<env>andhubspot-oauth-client-secret-<env>salesforce-oauth-client-id-<env>andsalesforce-oauth-client-secret-<env>zendesk-oauth-client-id-<env>andzendesk-oauth-client-secret-<env>
Non-prod Pulumi mounts those optional secrets and seeds disabled __NO_AUTH__
versions for unset provider credentials. Prod leaves optional marketplace
secrets unmounted by default; enable prod mounting with
services:publicBffOptionalMarketplaceSecretRefs or
PUBLIC_BFF_OPTIONAL_MARKETPLACE_SECRET_REFS set to all, none, or a
comma-separated list of secret refs such as
marketplace-oauth-state-secret,notion-oauth-client-id,notion-oauth-client-secret.
Selected OAuth providers must include both client id and client secret refs.
The services stack owns the optional prod Secret Manager containers; run it once
to create those containers, add real secret versions out-of-band, then enable the
selector. Every selected prod ref must already have a valid Secret Manager
version before the deploy that mounts it. If a prod optional container was
created out-of-band before Pulumi owned it, import or reconcile it before
selecting the ref.
Installed tenant OAuth and marketplace directory credentials are created at runtime under
threada-marketplace-oauth-<env>-<provider>-<tenant_hash>.
public-bff has a minimal custom writer role for that prefix, and
internal-runtime has conditioned Secret Manager accessor IAM for the same
prefix.
marketplace-oauth-state-secret-<env> must be a non-placeholder random secret
with at least 32 bytes, for example output from openssl rand -base64 32.
public-bff rejects the configured value at startup if it is too short or a
known placeholder; if the value is absent, install routes fail closed as
service-unavailable until the secret is mounted. Rotating this value invalidates
only in-flight OAuth install callbacks; installed provider credentials are
stored separately.
The stored runtime secret_ref intentionally points at the stable Secret
Manager secret resource, not at a specific version. Runtime secret resolution
therefore reads versions/latest, which keeps manual rotation and future token
refresh flows from pinning actions to stale credentials.
Runtime Binding
The OAuth callback upserts an enabled runtime integration with a deterministic
tenant/provider integration_id, the provider IntegrationType, and the
Secret Manager secret_ref. Reinstalling updates the same integration rather
than creating duplicates. Marketplace installs also store a
marketplace_capability_set and marketplace_allowed_action_types metadata
contract; runtime adapters must enforce that contract before executing any
provider write.
When install_source=marketplace_oauth or
install_source=marketplace_directory_credential, runtime action handlers fail
closed if marketplace_allowed_action_types is missing or does not include the
requested action. The check runs before provider-specific endpoint validation,
Secret Manager reads, token parsing, or outbound HTTP. That makes the
marketplace descriptor, OAuth scopes or directory credential contract, installed
integration metadata, and executable runtime action boundary one contract rather
than separate launch-check paperwork.
The same guard is wired into runtime-bound follow-on and store action handlers
such as Zapier, ServiceNow, Freshdesk, Datadog, PagerDuty, Okta, and Slack when
those actions are executed from marketplace-installed metadata.
The current v1 executable actions are:
- GitHub issue creation
- Atlassian/Jira issue creation and update through the governed Jira issue-context binding
- Notion page creation
- Asana task creation
- monday.com item creation
- Intercom ticket creation
- HubSpot contact creation/update
- Salesforce contact creation/update
- Zendesk ticket creation
- Freshdesk ticket creation
- Datadog event posting
- PagerDuty incident triggering
- Okta user upsert
- Slack channel post-message
Most current v1 installs are one enabled runtime integration per tenant/provider. Reinstalling the same provider updates that stable integration. Notion, Asana, monday.com, Intercom, and HubSpot are keyed by provider-owned identity, so reinstalling the same workspace or portal updates the same integration while different workspaces, monday.com boards, or portals produce separate runtime bindings. Supporting multiple GitHub orgs, Salesforce orgs, monday.com accounts beyond board-level mapping, Zendesk subdomains, or other provider accounts in the same Threada tenant requires adding provider account identity to the runtime integration key and provider-specific identity probes before marketplace launch gates can treat multi-account installs as supported.
Atlassian/Jira installs use OAuth 2.0 3LO with audience=api.atlassian.com
and prompt=consent, discover authorized Jira Cloud resources through
Atlassian’s accessible-resources endpoint, and store the selected Atlassian
Cloud ID as the provider resource key. Install URLs may include
atlassian_site, jira_site, subdomain, or resource; the BFF normalizes
that value to a single https://<site>.atlassian.net origin and fails closed
when the token grants no Jira site or multiple Jira sites without an explicit
site selection. The v1 runtime metadata uses the jira_issue_context_v1
capability set and an explicit jira_create_issue/jira_update_issue action
allowlist; the Jira runtime adapter still rejects any other Jira action before
reading credentials or contacting Atlassian. Broader Jira write claims still
require separate provider scopes, allowlist updates, and approval evidence.
Notion installs require the OAuth token response workspace_id; the callback
stores it as notion_workspace_id, uses it as the provider resource key, and
stores optional notion_workspace_name and notion_bot_id metadata. The
runtime endpoint URL is fixed to https://api.notion.com/v1. Token responses
without a stable workspace ID fail closed before Secret Manager storage or
runtime integration upsert. The v1 Notion action still requires the target
database to be explicitly shared with the Notion connection and mapped through
the governed parent_database_id action configuration.
Asana installs use the configured workspaces:read tasks:write OAuth scopes.
After token exchange, the callback calls Asana workspace discovery, binds the
runtime integration to the selected authorized workspace GID, and stores the
workspace ID, plus the workspace name when Asana returns it, in runtime
metadata and stores the workspace ID in the Secret Manager credential payload.
Install URLs may include asana_workspace, workspace, or resource; the BFF
requires one of those values when the token can see more than one workspace and
fails closed before Secret Manager storage or runtime integration upsert when
the selected workspace is not authorized. The runtime endpoint URL is fixed to
https://app.asana.com/api/1.0, and the runtime adapter revalidates that
host/path before reading credentials. Project mapping remains an explicit
governed runtime configuration and mismatched project/workspace payloads are
rejected.
monday.com installs use the configured boards:read boards:write OAuth scopes
and require a selected board_id before OAuth starts. The signed OAuth state
carries that board mapping plus optional group_id; after token exchange the
callback verifies the selected board and group through the monday.com API, then
stores the OAuth credential in Secret Manager under the selected board, upserts
a board-keyed runtime integration, and stores board_id and optional group_id
metadata. The runtime endpoint URL is fixed to https://api.monday.com/v2, and
the runtime adapter revalidates that endpoint and rejects payloads targeting a
different board or configured group before reading credentials.
OAuth credentials that include an expires_at value fail closed after expiry
unless that provider has an implemented runtime refresh path. Zendesk refreshes
expired marketplace OAuth credentials on use with the central OAuth client
configuration, writes the refreshed tenant credential as a new Secret Manager
version, and requires reconnect only when the refresh token is missing,
revoked, or rejected.
Salesforce installs use PKCE and require the OAuth token response
instance_url; the callback only accepts Salesforce-owned HTTPS instance hosts,
stores the normalized instance origin on the runtime integration, and runtime
execution revalidates that host before sending bearer-token requests.
Intercom v1 uses a Developer Hub app configured with the write_tickets
permission for governed ticket creation. Intercom permissions are configured in
Developer Hub; Threada does not send a dynamic scope parameter on the Intercom
OAuth authorization URL. After token exchange, the callback calls Intercom
/me, stores the returned app.id_code as intercom_workspace_id, uses that
workspace ID as the provider resource key, and stores the official Intercom API
origin on the runtime integration. If Intercom returns a regional workspace,
the runtime endpoint is pinned to the matching official regional API host, and
runtime execution revalidates that Intercom host before reading credentials.
Conversation read/sync, notes, tags, replies, Canvas apps, packaged knowledge
sync, and generic inbox automation require separate runtime actions, provider
permissions, allowlist updates, and App Store evidence before launch copy can
claim them.
HubSpot installs use the HubSpot OAuth 2026-03 token introspection endpoint
after code exchange to discover the authorized portal hub_id without placing
the access token in a URL path. The callback stores the hub_id as
hubspot_portal_id, uses it as the provider resource key, stores optional
portal domain/user/app metadata, and sets the runtime endpoint to
https://api.hubapi.com. HubSpot v1 install scopes are intentionally
contact-only (crm.objects.contacts.read and crm.objects.contacts.write).
Runtime execution only allows hubspot_upsert_contact for marketplace OAuth
installs and validates that the configured endpoint targets the HubSpot CRM API
origin before reading the stored access token.
Salesforce v1 uses the minimum OAuth scope set needed for REST contact writes, with the contact-only boundary enforced by runtime action allowlists because Salesforce OAuth scopes are not object-specific enough for that distinction. Deal, ticket, opportunity, and packaged knowledge workflows require broader provider scopes, runtime allowlist updates, and separate marketplace evidence before launch copy can claim them.
Zendesk v1 install scopes are tickets:write for governed ticket creation.
Zendesk uses account-subdomain-scoped global OAuth URLs, so the installer
requires a single Zendesk account subdomain and stores the normalized
https://<subdomain>.zendesk.com endpoint on the runtime integration. The v1
runtime binding allows zendesk_create_ticket and runtime token refresh;
ticket update, comments, macros, Marketplace request headers, revocation and
disconnect UX, and packaged knowledge workflows require separate runtime
allowlist and Zendesk review evidence before launch copy can claim them.
Launch/readiness checks must run after these runtime paths are deployed and provider accounts/apps are configured.