Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
0b9019d
v0.6.23: MCP fixes, remove local state in favor of server state, moth…
waleedlatif1 Apr 4, 2026
a54dcbe
v0.6.24: copilot feedback wiring, captcha fixes
waleedlatif1 Apr 4, 2026
28af223
v0.6.25: cloudwatch, cloudformation, live kb sync, linear fixes, post…
waleedlatif1 Apr 5, 2026
d889f32
v0.6.26: ui improvements, multiple response blocks, docx previews, ol…
waleedlatif1 Apr 5, 2026
316bc8c
v0.6.27: new triggers, mothership improvements, files archive, queuei…
waleedlatif1 Apr 7, 2026
3f508e4
v0.6.28: new docs, delete confirmation standardization, dagster integ…
waleedlatif1 Apr 7, 2026
d6ec115
v0.6.29: login improvements, posthog telemetry (#4026)
TheodoreSpeaks Apr 7, 2026
d7da35b
v0.6.30: slack trigger enhancements, connectors performance improveme…
waleedlatif1 Apr 8, 2026
cf233bb
v0.6.31: elevenlabs voice, trigger.dev fixes, cloud whitelabeling for…
waleedlatif1 Apr 8, 2026
f8f3758
v0.6.32: BYOK fixes, ui improvements, cloudwatch tools, jsm tools ext…
waleedlatif1 Apr 9, 2026
3c8bb40
v0.6.33: polling improvements, jsm forms tools, credentials reactquer…
waleedlatif1 Apr 9, 2026
d33acf4
v0.6.34: trigger.dev fixes, CI speedup, atlassian error extractor
waleedlatif1 Apr 9, 2026
4f40c4c
v0.6.35: additional jira fields, HITL docs, logs cleanup efficiency
waleedlatif1 Apr 10, 2026
cbfab1c
v0.6.36: new chunkers, sockets state machine, google sheets/drive/cal…
waleedlatif1 Apr 11, 2026
4309d06
v0.6.37: audit logs page, isolated-vm worker rotation, permission gro…
waleedlatif1 Apr 12, 2026
8b57476
v0.6.38: models page
waleedlatif1 Apr 12, 2026
e3d0e74
v0.6.39: billing fixes, tools audit, landing fix
waleedlatif1 Apr 13, 2026
0ac0539
v0.6.40: mothership tool loop, new skills, agiloft, STS, IAM integrat…
waleedlatif1 Apr 14, 2026
3838b6e
v0.6.41: webhooks fix, workers removal
waleedlatif1 Apr 14, 2026
fc07922
v0.6.42: mothership nested file reads, search modal improvements
waleedlatif1 Apr 14, 2026
3a1b1a8
v0.6.43: mothership billing idempotency, env var resolution fixes
waleedlatif1 Apr 14, 2026
46ffc49
v0.6.44: streamdown, mothership intelligence, excel extension
waleedlatif1 Apr 15, 2026
010435c
v0.6.45: superagent, csp, brightdata integration, gemini response for…
Sg312 Apr 15, 2026
c0bc62c
Merge pull request #4190 from simstudioai/staging
icecrasher321 Apr 16, 2026
387cc97
v0.6.46: mothership queueing, web vitals
waleedlatif1 Apr 16, 2026
2dbc7fd
v0.6.47: files focusing, documentation, opus 4.7
waleedlatif1 Apr 16, 2026
8a50f18
v0.6.48: import csv into tables, subflow fixes, CSP updates
waleedlatif1 Apr 16, 2026
dcf3302
v0.6.49: deploy sockets event, resolver, logs improvements, monday.co…
waleedlatif1 Apr 17, 2026
bc09865
v0.6.50: ppt/doc/pdf worker isolation, docs, chat, sidebar improvements
icecrasher321 Apr 18, 2026
5f56e46
v0.6.51: tables improvements, billing fixes, 404 pages, code hygiene
waleedlatif1 Apr 20, 2026
ca3bbf1
v0.6.52: data retention, docs updates, slack manifest generator, secu…
waleedlatif1 Apr 22, 2026
bbf400f
v0.6.53: permissions groups migration, docs updates
waleedlatif1 Apr 22, 2026
7c619e7
Merge pull request #4261 from simstudioai/staging
icecrasher321 Apr 22, 2026
64cfda5
v0.6.54: mothership tracing, db pool size increase
icecrasher321 Apr 22, 2026
7ca736a
v0.6.55: standardize monorepo conventions, api key hash, thinking tex…
waleedlatif1 Apr 23, 2026
6066fc1
v0.6.56: data retention improvements, tables column double click resi…
waleedlatif1 Apr 24, 2026
3422f64
Merge pull request #4285 from simstudioai/staging
waleedlatif1 Apr 24, 2026
595c4c3
Merge pull request #4293 from simstudioai/staging
TheodoreSpeaks Apr 24, 2026
d6c1bc2
v0.6.58: queue abort state machine improvement, contributing guide
icecrasher321 Apr 25, 2026
58a3ae2
v0.6.59: gpt 5.5, security hardening, parallel subagents rendering
icecrasher321 Apr 27, 2026
489f2d3
v0.6.60: copilot security improvements, slack canvas ops, retention j…
icecrasher321 Apr 27, 2026
6aa3fe3
v0.6.61: SAP integration, live URLs for browser use, 5xx error catego…
icecrasher321 Apr 29, 2026
ecbf5e5
Merge pull request #4342 from simstudioai/staging
TheodoreSpeaks Apr 29, 2026
2aaf2b7
v0.6.62: firecrawl parse, new gmail tools, trace improvements, tool f…
waleedlatif1 May 2, 2026
d445b9c
v0.6.63: knowledgebase UI, folder search in mothership
waleedlatif1 May 2, 2026
4bc6a17
v0.6.64: table limits env vars, workspace files improvements, integra…
waleedlatif1 May 3, 2026
5be12f8
v0.6.65: memory fix, image uploads in files
waleedlatif1 May 3, 2026
320f256
feat(credentials): add Atlassian service account credentials
TheodoreSpeaks May 4, 2026
06c99c4
improvement(credentials): tighten Atlassian service account plumbing
TheodoreSpeaks May 4, 2026
9db3fc9
docs(credentials): add Atlassian service account setup guide
TheodoreSpeaks May 4, 2026
7b318bf
docs(credentials): add Atlassian service account screenshots
TheodoreSpeaks May 4, 2026
dba7c56
docs(credentials): add Atlassian scope picker screenshot
TheodoreSpeaks May 4, 2026
cf33383
fix(credentials): address greptile feedback on Atlassian SA
TheodoreSpeaks May 5, 2026
9b143fa
fix(docs): move Atlassian screenshots to docs/public
TheodoreSpeaks May 5, 2026
7ea0c43
Merge remote-tracking branch 'origin/main' into feat/atlassian-servic…
TheodoreSpeaks May 5, 2026
a1e77ee
fix(credentials): address review feedback on Atlassian SA
TheodoreSpeaks May 5, 2026
8f1d4e7
Merge remote-tracking branch 'origin/staging' into feat/atlassian-ser…
TheodoreSpeaks May 5, 2026
287dade
chore: merge staging and bump API validation route baseline to 727
TheodoreSpeaks May 5, 2026
02a993a
perf(credentials): single-resolve in confluence spaces selector
TheodoreSpeaks May 5, 2026
e017f19
refactor(credentials): consolidate Atlassian SA creation into /api/cr…
TheodoreSpeaks May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions apps/docs/content/docs/en/integrations/atlassian-service-account.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
title: Atlassian Service Accounts
description: Set up an Atlassian service account with a scoped API token to use Jira and Confluence in Sim workflows
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'

Atlassian service accounts let your workflows authenticate to Jira and Confluence as a non-human bot user — independent of any individual employee's account. Each service account has its own email, its own permissions, and its own API tokens, all managed centrally in admin.atlassian.com.

This is the recommended way to use Jira and Confluence in production workflows: no one person's OAuth consent expires, the bot's permissions are auditable, and access can be revoked without touching anyone's personal account.

## Prerequisites

You need an Atlassian organization admin to create the service account. Service accounts are an Atlassian organization-level feature — they cannot be created from a regular user account.

## Setting Up the Service Account

### 1. Create the Service Account

<Steps>
<Step>
Open [admin.atlassian.com](https://admin.atlassian.com/) and go to **Directory** → **Service accounts**

{/* TODO(screenshot): admin.atlassian.com directory page with the "Service accounts" tab highlighted */}
</Step>
<Step>
Click **Create service account**, give it a name (e.g. `sim-jira-bot`), and finish creation
</Step>
<Step>
Grant the service account access to the Atlassian sites and products it needs. Open the service account, go to **Product access**, and add Jira and/or Confluence on the relevant site

{/* TODO(screenshot): service account "Product access" tab showing Jira granted on a site */}
</Step>
</Steps>

<Callout type="info">
The service account inherits permissions from the project/space roles you grant it — exactly like a human user. If a workflow needs to write to a specific Jira project, give the service account write access to that project in Jira's project settings.
</Callout>

### 2. Create a Scoped API Token

<Steps>
<Step>
From the service account's page in admin.atlassian.com, open the **API tokens** tab and click **Create API token**

{/* TODO(screenshot): service account API tokens tab with "Create API token" button */}
</Step>
<Step>
Choose **API token** as the authentication type (not OAuth 2.0 — Sim uses the API token flow)

<div className="flex justify-center">
<Image
src="/static/credentials/atlassian/admin-auth-type-picker.png"
alt="Atlassian admin — Choose authentication type with API token selected"
width={700}
height={500}
className="my-4"
/>
</div>
</Step>
<Step>
Select the scopes the token needs. The minimum set Sim's Jira and Confluence blocks expect is:

**Jira (granular):**
```
read:jira-user
read:jira-work
write:jira-work
```

**Confluence (granular):**
```
read:confluence-content.all
read:confluence-space.summary
write:confluence-content
read:page:confluence
write:page:confluence
```

Add more scopes only if you need the corresponding operations (delete, manage webhooks, etc.). The full list of scopes Sim's blocks may use is documented in [Atlassian's developer reference](https://developer.atlassian.com/cloud/jira/platform/scopes-for-oauth-2-3LO-and-forge-apps/).

<div className="flex justify-center">
<Image
src="/static/credentials/atlassian/admin-scope-picker.png"
alt="Atlassian token scope picker filtered to App: Jira and Scope type: Classic"
width={1000}
height={600}
className="my-4"
/>
</div>

<Callout type="info">
Use the **App** and **Scope type** filters to narrow the list to the scopes you need. Filter by `App: Jira` (or `Confluence`) and `Scope type: Classic` to find the three core Jira scopes; switch to **Granular** if your org doesn't expose Classic.
</Callout>
</Step>
<Step>
Copy the token when it's shown. Atlassian only displays it once — if you close the dialog, you'll have to create a new token.
</Step>
</Steps>

<Callout type="warn">
The API token is bearer credentials for the service account. Treat it like a password — do not commit it to source control or share it publicly. Sim encrypts the token at rest.
</Callout>

### 3. Find Your Site Domain

Your Atlassian site domain is the URL you use to access Jira or Confluence in your browser — for example, `your-team.atlassian.net`. Open Jira or Confluence, look at the address bar, and copy the part before the first `/`.

## Adding the Service Account to Sim

<Steps>
<Step>
Open your workspace **Settings** and go to the **Integrations** tab
</Step>
<Step>
Search for "Atlassian Service Account" and click it

{/* TODO(screenshot): Integrations page with "Atlassian Service Account" in the service list */}
</Step>
<Step>
Paste the API token, enter the site domain (e.g. `your-team.atlassian.net`), and optionally set a display name and description

<div className="flex justify-center">
<Image
src="/static/credentials/atlassian/sim-add-modal.png"
alt="Add Atlassian Service Account dialog with API token and site domain filled in"
width={420}
height={560}
className="my-6"
/>
</div>
</Step>
<Step>
Click **Add Service Account**. Sim verifies the token by calling Atlassian's `/myself` endpoint through the gateway — if it fails, you'll see a specific error explaining what went wrong.
</Step>
</Steps>

The token, domain, and discovered cloudId are encrypted before being stored.

## Using the Service Account in Workflows

Add a Jira or Confluence block to your workflow. In the credential dropdown, your Atlassian service account appears alongside any OAuth credentials. Select it and configure the block as you normally would.

<div className="flex justify-center">
<Image
src="/static/credentials/atlassian/sim-jira-block-credential.png"
alt="Jira block in a workflow with the Atlassian service account selected as the credential"
width={1000}
height={500}
className="my-4"
/>
</div>

The block calls Atlassian's API gateway (`api.atlassian.com/ex/jira/{cloudId}/...`) using the service account's token. There's no impersonation step — the service account acts as itself, with whatever permissions you granted it in admin.atlassian.com.

<FAQ items={[
{ question: "Why an API token instead of OAuth?", answer: "API tokens for service accounts don't have a 1-hour expiry and don't require any user to consent. They're issued by an org admin and are stable until you revoke them — which is what you want for an automated workflow." },
{ question: "Can a regular user create a service account?", answer: "No. Service accounts are an Atlassian organization-level feature and only an organization admin can create them." },
{ question: "Can the same service account work with both Jira and Confluence?", answer: "Yes — give the service account access to both products on your site, and include scopes for both when you create the API token. Then connect it once in Sim and use it from either Jira or Confluence blocks." },
{ question: "What if my workflow needs different permissions than the token has?", answer: "Either widen the token's scopes (revoke it and create a new one with more scopes), or grant the service account higher project/space roles in Jira or Confluence. Scope failures look like 401/403 errors with descriptive messages." },
{ question: "How do I rotate the API token?", answer: "Create a new token from the same service account in admin.atlassian.com, update the credential in Sim with the new token, and once it's working, revoke the old one." },
{ question: "Does this work with Atlassian Data Center / on-prem?", answer: "No — this integration uses Atlassian Cloud's API gateway (`api.atlassian.com`). For Data Center, use the OAuth flow or set up a self-hosted bot user." },
]} />
2 changes: 1 addition & 1 deletion apps/docs/content/docs/en/integrations/meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"title": "Integrations",
"pages": ["index", "google-service-account"],
"pages": ["index", "google-service-account", "atlassian-service-account"],
"defaultOpen": false
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions apps/sim/app/api/auth/oauth/token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types'
import {
getAtlassianServiceAccountSecret,
getCredential,
getOAuthToken,
getServiceAccountToken,
Expand Down Expand Up @@ -118,6 +120,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
}

try {
if (resolved.providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) {
const secret = await getAtlassianServiceAccountSecret(resolved.credentialId)
return NextResponse.json(
{
accessToken: secret.apiToken,
cloudId: secret.cloudId,
domain: secret.domain,
},
{ status: 200 }
)
}
const accessToken = await getServiceAccountToken(
resolved.credentialId,
scopes ?? [],
Expand Down
58 changes: 58 additions & 0 deletions apps/sim/app/api/auth/oauth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {
isMicrosoftProvider,
PROACTIVE_REFRESH_THRESHOLD_DAYS,
} from '@/lib/oauth/microsoft'
import {
ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID,
ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE,
} from '@/lib/oauth/types'

const logger = createLogger('OAuthUtilsAPI')

Expand Down Expand Up @@ -44,6 +48,7 @@ export interface ResolvedCredential {
usedCredentialTable: boolean
credentialType?: string
credentialId?: string
providerId?: string
}

/**
Expand All @@ -61,6 +66,7 @@ export async function resolveOAuthAccountId(
type: credential.type,
accountId: credential.accountId,
workspaceId: credential.workspaceId,
providerId: credential.providerId,
})
.from(credential)
.where(eq(credential.id, credentialId))
Expand All @@ -73,6 +79,7 @@ export async function resolveOAuthAccountId(
credentialId: credentialRow.id,
credentialType: 'service_account',
workspaceId: credentialRow.workspaceId,
providerId: credentialRow.providerId ?? undefined,
usedCredentialTable: true,
}
}
Expand Down Expand Up @@ -208,6 +215,53 @@ export async function getServiceAccountToken(
return tokenData.access_token
}

interface AtlassianServiceAccountSecret {
type: typeof ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE
apiToken: string
domain: string
cloudId: string
atlassianAccountId?: string
}

/**
* Loads the decrypted Atlassian service account secret blob for a credential.
* Throws if the credential is missing or not an Atlassian service account.
*/
export async function getAtlassianServiceAccountSecret(
credentialId: string
): Promise<AtlassianServiceAccountSecret> {
const [credentialRow] = await db
.select({ encryptedServiceAccountKey: credential.encryptedServiceAccountKey })
.from(credential)
.where(eq(credential.id, credentialId))
.limit(1)

if (!credentialRow?.encryptedServiceAccountKey) {
throw new Error('Atlassian service account secret not found')
}

const { decrypted } = await decryptSecret(credentialRow.encryptedServiceAccountKey)
const parsed = JSON.parse(decrypted) as AtlassianServiceAccountSecret
if (
parsed.type !== ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE ||
!parsed.apiToken ||
!parsed.cloudId
) {
throw new Error('Stored Atlassian service account secret is malformed')
}
return parsed
}

/**
* For Atlassian service accounts, the API token IS the access token —
* blocks call api.atlassian.com/ex/jira/{cloudId}/... with `Authorization: Bearer {apiToken}`.
* No exchange or refresh is needed; we just decrypt and return the raw token.
*/
export async function getAtlassianServiceAccountToken(credentialId: string): Promise<string> {
const secret = await getAtlassianServiceAccountSecret(credentialId)
return secret.apiToken
}

/**
* Safely inserts an account record, handling duplicate constraint violations gracefully.
* If a duplicate is detected (unique constraint violation), logs a warning and returns success.
Expand Down Expand Up @@ -374,6 +428,10 @@ export async function refreshAccessTokenIfNeeded(
}

if (resolved.credentialType === 'service_account' && resolved.credentialId) {
if (resolved.providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) {
logger.info(`[${requestId}] Using Atlassian service account token for credential`)
return getAtlassianServiceAccountToken(resolved.credentialId)
}
if (!scopes?.length) {
throw new Error('Scopes are required for service account credentials')
}
Expand Down
Loading
Loading