Skip to content

Commit 506307d

Browse files
committed
Merge remote-tracking branch 'origin/staging' into feat/saved-database-connections
# Conflicts: # apps/sim/tools/generated/tool-metadata.ts
2 parents ebb8686 + 3e5200f commit 506307d

83 files changed

Lines changed: 1527 additions & 141 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/docs/content/docs/integrations/apollo.mdx‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,50 @@ import { ReferenceDetails } from "@/components/ui/reference-details"
1111

1212
{/* MANUAL-CONTENT-START:intro */}
1313
Use [Apollo.io](https://apollo.io/) in Sim to search and enrich people and companies, manage CRM contacts and accounts, add contacts to sequences, and create follow-up tasks. Single-record and bulk enrichment operations are documented below.
14+
15+
Connect an Apollo account with OAuth, or save an API key as an Apollo credential. Existing API-key connections and workflows continue to work. OAuth requires an Apollo administrator or a user with permission to authorize third-party apps. Your Apollo plan, permissions, and credits still apply.
16+
17+
For self-hosting, register a public partner OAuth app in the [Apollo developer portal](https://developer.apollo.io/), configure `APOLLO_CLIENT_ID` and `APOLLO_CLIENT_SECRET`, and register `https://<your-domain>/api/auth/oauth2/callback/apollo` as a redirect URL. Enable the scopes listed below. API-key credentials remain available without an OAuth client.
1418
{/* MANUAL-CONTENT-END */}
1519

20+
## OAuth scopes [#oauth-scopes]
21+
22+
Sim requests these scopes when someone connects an Apollo account. For a self-hosted deployment, register your own app with the provider using the settings below. See [Integrations and OAuth](/platform/self-hosting/integrations-oauth) for setup.
23+
24+
| Setting | Value |
25+
| ------- | ----- |
26+
| Redirect URI | `<NEXT_PUBLIC_APP_URL>/api/auth/oauth2/callback/apollo` |
27+
| Environment variables | `APOLLO_CLIENT_ID`, `APOLLO_CLIENT_SECRET` |
28+
29+
| Scope | Description |
30+
| ----- | ----------- |
31+
| `read_user_profile` | View your Apollo profile |
32+
| `mixed_people_api_search` | Search Apollo people |
33+
| `people_match` | Enrich a person in Apollo |
34+
| `people_bulk_match` | Enrich people in bulk in Apollo |
35+
| `mixed_companies_search` | Search Apollo companies |
36+
| `organizations_enrich` | Enrich an Apollo organization |
37+
| `organizations_bulk_enrich` | Enrich organizations in bulk in Apollo |
38+
| `contact_write` | Create Apollo contacts |
39+
| `contact_update` | Update Apollo contacts |
40+
| `contacts_search` | Search Apollo contacts |
41+
| `contacts_bulk_create` | Create Apollo contacts in bulk |
42+
| `contacts_bulk_update` | Update Apollo contacts in bulk |
43+
| `account_write` | Create Apollo accounts |
44+
| `account_update` | Update Apollo accounts |
45+
| `accounts_search` | Search Apollo accounts |
46+
| `account_bulk_create` | Create Apollo accounts in bulk |
47+
| `account_stages_update` | Update Apollo account stages |
48+
| `opportunity_write` | Create Apollo deals |
49+
| `opportunities_list` | List Apollo deals |
50+
| `opportunity_read` | View Apollo deals |
51+
| `opportunity_update` | Update Apollo deals |
52+
| `emailer_campaigns_search` | Search Apollo sequences |
53+
| `emailer_campaigns_add_contact_ids` | Add contacts to Apollo sequences |
54+
| `tasks_create` | Create Apollo tasks |
55+
| `tasks_list` | Search Apollo tasks |
56+
| `email_accounts_list` | View Apollo email accounts |
57+
1658
## Use the block [#usage-instructions]
1759

1860
Search for people and companies, enrich contact data, manage your CRM contacts and accounts, add contacts to sequences, and create tasks.

‎apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ GitHub workflow blocks and knowledge-base token connections continue to use pers
393393
| Help Scout | `HELPSCOUT_CLIENT_ID` / `HELPSCOUT_CLIENT_SECRET` | `helpscout` |
394394
| HubSpot | `HUBSPOT_CLIENT_ID` / `HUBSPOT_CLIENT_SECRET` | `hubspot` |
395395
| Salesforce | `SALESFORCE_CLIENT_ID` / `SALESFORCE_CLIENT_SECRET` | `salesforce` |
396+
| Apollo | `APOLLO_CLIENT_ID` / `APOLLO_CLIENT_SECRET` | `apollo` |
396397
| Pipedrive | `PIPEDRIVE_CLIENT_ID` / `PIPEDRIVE_CLIENT_SECRET` | `pipedrive` |
397398
| Attio | `ATTIO_CLIENT_ID` / `ATTIO_CLIENT_SECRET` | `attio` |
398399
| Zoho Desk | `ZOHO_CLIENT_ID` / `ZOHO_CLIENT_SECRET` | `zoho-desk` |

‎apps/docs/openapi-v2-resources.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18513,6 +18513,7 @@
1851318513
"ramp",
1851418514
"helpscout",
1851518515
"hubspot",
18516+
"apollo",
1851618517
"linkedin",
1851718518
"instagram",
1851818519
"salesforce",

‎apps/docs/openapi-v2-workflows.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15969,7 +15969,7 @@
1596915969
"failed",
1597015970
"cancelled"
1597115971
],
15972-
"description": "Current or terminal run status. `redacting` is transient, reported while a finished run's output is being scrubbed. `paused` means the run is waiting at a human or time pause, or a resume attempt did not complete. The single-run response provides the pause kind, resume time, and any automatic-resume waiting reason."
15972+
"description": "Current or terminal run status. `redacting` is transient, reported while a finished run's output is being scrubbed. `paused` means the run has a human or time pause that can still be resumed; once the run has finished and every pause point has resumed or failed, it reports the finished status. The single-run response provides the pause kind, resume time, and any automatic-resume waiting reason."
1597315973
},
1597415974
"trigger": {
1597515975
"type": "string",
@@ -16468,7 +16468,7 @@
1646816468
"cancelled",
1646916469
"queued"
1647016470
],
16471-
"description": "Current or terminal run status. `redacting` is transient, reported while a finished run's output is being scrubbed. `paused` means the run is waiting at a human or time pause, or a resume attempt did not complete. The single-run response provides the pause kind, resume time, and any automatic-resume waiting reason."
16471+
"description": "Current or terminal run status. `redacting` is transient, reported while a finished run's output is being scrubbed. `paused` means the run has a human or time pause that can still be resumed; once the run has finished and every pause point has resumed or failed, it reports the finished status. The single-run response provides the pause kind, resume time, and any automatic-resume waiting reason."
1647216472
},
1647316473
"trigger": {
1647416474
"anyOf": [

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx‎

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflow
2828
import { BrandIcon } from '@/blocks/brand-icon'
2929
import type { SubBlockConfig } from '@/blocks/types'
3030
import { useWorkspaceCredential, useWorkspaceCredentials } from '@/hooks/queries/credentials'
31+
import { useIntegrationAvailability } from '@/hooks/queries/integration-availability'
3132
import { useOAuthCredentials } from '@/hooks/queries/oauth/credentials'
3233
import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers'
34+
import { useIntegrationAccess } from '@/hooks/use-integration-access'
3335
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
36+
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3437

3538
interface CredentialSelectorProps {
3639
blockId: string
@@ -57,6 +60,7 @@ export function CredentialSelector({
5760
const [editingValue, setEditingValue] = useState('')
5861
const [isEditing, setIsEditing] = useState(false)
5962
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
63+
const blockType = useWorkflowStore((state) => state.blocks[blockId]?.type)
6064
const [storeValue, setStoreValue] = useSubBlockValue<string | null>(blockId, subBlock.id)
6165

6266
const label = subBlock.placeholder || 'Select credential'
@@ -100,6 +104,20 @@ export function CredentialSelector({
100104

101105
const credentialKind = subBlock.credentialKind
102106
const isMergedKinds = credentialKind === 'any'
107+
const { getAccess } = useIntegrationAccess(workspaceId, {
108+
enabled: !isAllCredentials && credentialKind !== 'service-account',
109+
})
110+
const { data: integrationAvailability, isSuccess: isAvailabilityReady } =
111+
useIntegrationAvailability({
112+
enabled: !isAllCredentials && credentialKind !== 'service-account',
113+
})
114+
const oauthConnectAvailable =
115+
Boolean(blockType && getAccess(blockType) === 'available') &&
116+
isAvailabilityReady &&
117+
(integrationAvailability?.oauthServiceAvailability.some(
118+
({ providerId, available }) => providerId === effectiveProviderId && available
119+
) ??
120+
false)
103121

104122
const credentials = useMemo(() => {
105123
// A service-account picker lists only the reusable service-account
@@ -233,8 +251,9 @@ export function CredentialSelector({
233251
setShowSetupModal(true)
234252
return
235253
}
254+
if (!oauthConnectAvailable) return
236255
setShowConnectModal(true)
237-
}, [credentialKind])
256+
}, [credentialKind, oauthConnectAvailable])
238257

239258
const getProviderIcon = useCallback((providerName: OAuthProvider) => {
240259
const { baseProvider } = parseProvider(providerName)
@@ -278,7 +297,9 @@ export function CredentialSelector({
278297
// Suppress the setup action when the service-account flow is preview-gated
279298
// for this viewer (a custom Slack bot needs slack_v2) — existing accounts
280299
// above stay selectable.
281-
if (credentialKind !== 'service-account' || !serviceAccountConnectHidden) {
300+
if (
301+
credentialKind === 'service-account' ? !serviceAccountConnectHidden : oauthConnectAvailable
302+
) {
282303
options.push({
283304
label:
284305
credentialKind === 'service-account'
@@ -302,6 +323,7 @@ export function CredentialSelector({
302323
credentialKind,
303324
subBlock.credentialLabels,
304325
serviceAccountConnectHidden,
326+
oauthConnectAvailable,
305327
serviceAccountTarget,
306328
provider,
307329
getProviderIcon,
@@ -323,11 +345,15 @@ export function CredentialSelector({
323345
section: labels?.oauthGroup ?? `${getProviderName(provider)} accounts`,
324346
items: [
325347
...credentials.filter((c) => c.type !== 'service_account').map(toOption),
326-
{
327-
label: labels?.oauthConnect ?? `Connect ${getProviderName(provider)} account`,
328-
value: '__connect_account__',
329-
iconElement: <SquareArrowUpRight className='size-3' />,
330-
},
348+
...(oauthConnectAvailable
349+
? [
350+
{
351+
label: labels?.oauthConnect ?? `Connect ${getProviderName(provider)} account`,
352+
value: '__connect_account__',
353+
iconElement: <SquareArrowUpRight className='size-3' />,
354+
},
355+
]
356+
: []),
331357
],
332358
},
333359
{
@@ -355,6 +381,7 @@ export function CredentialSelector({
355381
subBlock.credentialLabels,
356382
credentials,
357383
serviceAccountConnectHidden,
384+
oauthConnectAvailable,
358385
serviceAccountTarget,
359386
provider,
360387
getProviderIcon,
@@ -407,10 +434,6 @@ export function CredentialSelector({
407434
const handleComboboxChange = useCallback(
408435
(value: string) => {
409436
if (value === '__connect_account__') {
410-
if (isMergedKinds) {
411-
setShowConnectModal(true)
412-
return
413-
}
414437
handleAddCredential()
415438
return
416439
}
@@ -432,14 +455,7 @@ export function CredentialSelector({
432455
setIsEditing(true)
433456
setEditingValue(value)
434457
},
435-
[
436-
isAllCredentials,
437-
isMergedKinds,
438-
allWorkspaceCredentials,
439-
credentials,
440-
handleAddCredential,
441-
handleSelect,
442-
]
458+
[isAllCredentials, allWorkspaceCredentials, credentials, handleAddCredential, handleSelect]
443459
)
444460

445461
return (
@@ -470,31 +486,32 @@ export function CredentialSelector({
470486
<span className='mr-1.5 inline-block size-[6px] rounded-xs bg-[var(--caution)]' />
471487
{dataversePolicy.message}
472488
</div>
473-
{!dataversePolicy.hasInvalidEnvironment && (
474-
<Chip
475-
variant='primary'
476-
fullWidth
477-
onClick={() => {
478-
if (dataversePolicy.requiresSeparateCredential) {
479-
setShowConnectModal(true)
480-
return
481-
}
482-
writeOAuthReturnContext({
483-
origin: 'workflow',
484-
workflowId: activeWorkflowId || '',
485-
displayName: selectedCredential?.name ?? getProviderName(provider),
486-
providerId: effectiveProviderId,
487-
preCount: credentials.filter((c) => c.type !== 'service_account').length,
488-
workspaceId,
489-
reconnect: true,
490-
requestedAt: Date.now(),
491-
})
492-
setShowOAuthModal(true)
493-
}}
494-
>
495-
{dataversePolicy.actionLabel}
496-
</Chip>
497-
)}
489+
{!dataversePolicy.hasInvalidEnvironment &&
490+
(!dataversePolicy.requiresSeparateCredential || oauthConnectAvailable) && (
491+
<Chip
492+
variant='primary'
493+
fullWidth
494+
onClick={() => {
495+
if (dataversePolicy.requiresSeparateCredential) {
496+
setShowConnectModal(true)
497+
return
498+
}
499+
writeOAuthReturnContext({
500+
origin: 'workflow',
501+
workflowId: activeWorkflowId || '',
502+
displayName: selectedCredential?.name ?? getProviderName(provider),
503+
providerId: effectiveProviderId,
504+
preCount: credentials.filter((c) => c.type !== 'service_account').length,
505+
workspaceId,
506+
reconnect: true,
507+
requestedAt: Date.now(),
508+
})
509+
setShowOAuthModal(true)
510+
}}
511+
>
512+
{dataversePolicy.actionLabel}
513+
</Chip>
514+
)}
498515
</div>
499516
)}
500517

‎apps/sim/blocks/blocks/apollo.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,5 +1600,6 @@ export const ApolloV2Block: BlockConfig = {
16001600
type: 'oauth-input',
16011601
serviceId: 'apollo',
16021602
title: 'Apollo Credential',
1603+
credentialKind: 'any',
16031604
}),
16041605
}

‎apps/sim/blocks/utils.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ interface SavedCredentialPicker {
6262
type: 'oauth-input'
6363
serviceId: SavedCredentialService
6464
title: string
65+
credentialKind?: 'service-account' | 'any'
6566
}
6667

6768
interface SavedCredentialOptions {
@@ -160,7 +161,7 @@ export function withSavedCredential(
160161
0,
161162
{
162163
...picker,
163-
credentialKind: 'service-account',
164+
credentialKind: picker.credentialKind ?? 'service-account',
164165
canonicalParamId: 'oauthCredential',
165166
mode: 'basic',
166167
// 'AWS Credential' → 'Select AWS credential': sentence case, brand casing kept.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
slug: saved-credentials-and-access
3+
title: 'Connect once and control how credentials are used'
4+
description: 'Sim workflows can reuse saved credentials instead of repeated keys. Keyless cloud connections and read-only API keys give teams more control.'
5+
date: '2026-11-10T20:00:00Z'
6+
updated: '2026-11-12T05:00:30Z'
7+
authors: [waleed]
8+
tags: [Workflows, Security]
9+
ogImage: /changelog/credentials-gateway-v1.jpg
10+
ogAlt: 'Monochrome illustration of two stone gateways overlooking mountain ridges'
11+
technical: false
12+
draft: false
13+
release:
14+
versions: [v0.9.53, v0.9.54]
15+
---
16+
17+
<ChangelogImage
18+
src="/changelog/credentials-gateway-v1.jpg"
19+
alt="Monochrome illustration of two stone gateways overlooking mountain ridges"
20+
width="1672"
21+
height="941"
22+
/>
23+
24+
Save a connection once and select it in your workflow blocks. Keys and connection details stay in one place instead of being copied into every block, and you choose who can use the saved credential.
25+
26+
Introduced in v0.9.53 and expanded in v0.9.54, saved credentials cover more services that previously asked for keys directly. Existing blocks keep their original settings. To use a saved connection, replace a legacy block with its current version and select the credential.
27+
28+
### Connect without a long-lived key
29+
30+
In v0.9.53, supported cloud services can trust Sim to obtain temporary access. Your cloud administrator configures the trust and permissions; self-hosted deployments also need workload identity configured. See the [AWS](https://docs.sim.ai/integrations/aws-credentials) and [Databricks](https://docs.sim.ai/integrations/databricks-credentials) guides.
31+
32+
### Give scripts only the access they need
33+
34+
Create a **Read only** API key with an expiration date under **Sim API keys** in account or workspace settings. Introduced in v0.9.53 and generally available in v0.9.54, these keys can inspect information without changing workspace resources or starting, resuming, or cancelling workflows.
35+
36+
Allowed reads can still consume credits or call connected services. Existing keys retain full access. [API key access and expiration](https://docs.sim.ai/api-reference/authentication#key-access).
37+
38+
Technical releases: [v0.9.53](https://github.com/simstudioai/sim/releases/tag/v0.9.53), [v0.9.54](https://github.com/simstudioai/sim/releases/tag/v0.9.54).

0 commit comments

Comments
 (0)