Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8f0ef58
v0.5.7: combobox selectors, usage indicator, workflow loading race co…
icecrasher321 Nov 18, 2025
31c34b2
v0.5.8: notifications, billing, ui changes, store loading state machine
icecrasher321 Nov 20, 2025
842ef27
v0.5.9: add backwards compatibility for agent messages array
waleedlatif1 Nov 20, 2025
b7e814b
v0.5.10: copilot upgrade, preprocessor, logs search, UI, code hygiene
waleedlatif1 Nov 21, 2025
ebcd243
v0.5.11: stt, videogen, vllm, billing fixes, new models
waleedlatif1 Nov 25, 2025
1d08796
v0.5.12: memory optimizations, sentry, incidentio, posthog, zendesk, …
waleedlatif1 Nov 29, 2025
8c32ad4
v0.5.13: polling fixes, generic agent search tool, status page, smtp,…
waleedlatif1 Dec 1, 2025
54cc937
v0.5.14: fix issue with teams, google selectors + cleanup code
icecrasher321 Dec 1, 2025
774e5d5
v0.5.15: add tools, revert subblock prop change
icecrasher321 Dec 1, 2025
e157ce5
v0.5.16: MCP fixes, code refactors, jira fixes, new mistral models
waleedlatif1 Dec 3, 2025
3187493
v0.5.17: modals, billing fixes, bun update, zoom, dropbox, kalshi, po…
waleedlatif1 Dec 4, 2025
6cd078b
v0.5.18: ui fixes, nextjs16, workspace notifications, admin APIs, loa…
icecrasher321 Dec 5, 2025
929a352
fix(build): added trigger.dev sdk mock to tests (#2216)
icecrasher321 Dec 5, 2025
12c4c2d
v0.5.19: copilot fix
icecrasher321 Dec 5, 2025
ebef5f3
v0.5.20: google slides, ui fixes, subflow resizing improvements
waleedlatif1 Dec 6, 2025
c27c233
v0.5.21: google groups, virtualized code viewer, ui, autolayout, docs…
waleedlatif1 Dec 8, 2025
d480057
fix(migration): migration got removed by force push (#2253)
icecrasher321 Dec 8, 2025
52edbea
v0.5.22: rss feed trigger, sftp tool, billing fixes, 413 surfacing, c…
waleedlatif1 Dec 9, 2025
b7bbef8
v0.5.23: kb, logs, general ui improvements, token bucket rate limits,…
waleedlatif1 Dec 10, 2025
18b7032
v0.5.24: agent tool and UX improvements, redis service overhaul (#2291)
waleedlatif1 Dec 10, 2025
b5da613
v0.5.25: minor ui improvements, copilot billing fix
icecrasher321 Dec 11, 2025
3fbd57c
v0.5.26: tool fixes, templates and knowledgebase fixes, deployment ve…
waleedlatif1 Dec 11, 2025
e24f31c
v0.5.27: sidebar updates, ssrf patches, gpt-5.2, stagehand fixes
waleedlatif1 Dec 11, 2025
f526c36
v0.5.28: tool fixes, sqs, spotify, nextjs update, component playground
waleedlatif1 Dec 13, 2025
a0fb889
v0.5.29: chat voice mode, opengraph for docs, option to disable auth
waleedlatif1 Dec 14, 2025
86c468d
feat: service now table crud and import set api operations
Dec 14, 2025
842200b
fix(docs): clarify working directory for drizzle migration (#2375)
Shivam-002 Dec 15, 2025
fcf52ac
fix(landing): prevent url encoding for spaces for footer links (#2376)
Chadha93 Dec 15, 2025
25afacb
v0.5.30: vllm fixes, permissions fixes, isolated vms for code executi…
waleedlatif1 Dec 16, 2025
f9cfca9
v0.5.31: add zod as direct dep
icecrasher321 Dec 16, 2025
eb42db5
feat-service now oauth
Dec 16, 2025
f16a406
Update bun.lock
Pbonmars-20031006 Dec 16, 2025
f3537df
Update bun.lock
Pbonmars-20031006 Dec 16, 2025
a19a577
Create bun.lock
Pbonmars-20031006 Dec 16, 2025
ed574af
Merge pull request #2399 from Pbonmars-20031006/feature_service_now
Pbonmars-20031006 Dec 16, 2025
a23abd1
feat(servicenow): add OAuth 2.0 authentication support
Dec 16, 2025
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"

Then run the migrations:
```bash
cd apps/sim # Required so drizzle picks correct .env file
bunx drizzle-kit migrate --config=./drizzle.config.ts
```

Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/docs/en/tools/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"sentry",
"serper",
"sftp",
"servicenow",
"sharepoint",
"shopify",
"slack",
Expand Down
138 changes: 138 additions & 0 deletions apps/docs/content/docs/en/tools/servicenow.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: ServiceNow
description: Create, read, update, and delete ServiceNow records
---

import { BlockInfoCard } from "@/components/ui/block-info-card"

<BlockInfoCard
type="servicenow"
color="#81B5A1"
/>

{/* MANUAL-CONTENT-START:intro */}
[ServiceNow](https://www.servicenow.com/) is a leading cloud-based platform that provides IT service management (ITSM), IT operations management (ITOM), and IT business management (ITBM) solutions. ServiceNow helps organizations automate workflows, manage digital operations, and deliver exceptional employee and customer experiences.

ServiceNow offers a comprehensive platform for managing IT services, incidents, problems, changes, and other business processes. With its flexible table-based architecture, ServiceNow can be customized to manage virtually any type of record or business process across an organization.

Key features of ServiceNow include:

- **IT Service Management**: Comprehensive ITSM capabilities including incident, problem, change, and request management
- **Custom Tables**: Flexible table-based architecture allowing organizations to create custom tables for any business process
- **Workflow Automation**: Powerful workflow engine for automating business processes and approvals
- **REST API**: Robust REST API for programmatic access to ServiceNow data and operations

In Sim, the ServiceNow integration enables your agents to interact directly with ServiceNow records and tables. This allows for powerful automation scenarios such as automated incident creation, ticket updates, user management, and custom table operations. Your agents can create, read, update, and delete records in any ServiceNow table (incidents, tasks, users, custom tables, etc.) programmatically. This integration bridges the gap between your AI workflows and your ServiceNow instance, enabling seamless automation of IT service management tasks and business processes.
{/* MANUAL-CONTENT-END */}


## Usage Instructions

Integrate ServiceNow into the workflow. Can create, read, update, and delete records in any ServiceNow table (incidents, tasks, users, etc.). Supports OAuth 2.0 (recommended) or Basic Auth authentication.



## Tools

### `servicenow_create`

Create a new record in a ServiceNow table

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com\) |
| `authMethod` | string | Yes | Authentication method: `oauth` or `basic` |
| `credential` | string | No | ServiceNow OAuth credential ID \(required when authMethod is `oauth`\) |
| `username` | string | No | ServiceNow username \(required when authMethod is `basic`\) |
| `password` | string | No | ServiceNow password \(required when authMethod is `basic`\) |
| `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user\) |
| `fields` | json | Yes | Fields to set on the record \(JSON object\) |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `record` | json | Created ServiceNow record with sys_id and other fields |
| `metadata` | json | Operation metadata including record count |

### `servicenow_read`

Read records from a ServiceNow table

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `instanceUrl` | string | Yes | ServiceNow instance URL |
| `username` | string | Yes | ServiceNow username |
| `password` | string | Yes | ServiceNow password |
| `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user\) |
| `sysId` | string | No | Specific record sys_id to retrieve |
| `number` | string | No | Record number \(e.g., INC0010001\) |
| `query` | string | No | Encoded query string \(e.g., "active=true^priority=1"\) |
| `limit` | number | No | Maximum number of records to return |
| `fields` | string | No | Comma-separated list of fields to return |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `records` | array | Array of ServiceNow records |
| `metadata` | json | Operation metadata including record count |

### `servicenow_update`

Update an existing record in a ServiceNow table

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `instanceUrl` | string | Yes | ServiceNow instance URL |
| `username` | string | Yes | ServiceNow username |
| `password` | string | Yes | ServiceNow password |
| `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user\) |
| `sysId` | string | Yes | Record sys_id to update |
| `fields` | json | Yes | Fields to update \(JSON object\) |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `record` | json | Updated ServiceNow record |
| `metadata` | json | Operation metadata including updated fields |

### `servicenow_delete`

Delete a record from a ServiceNow table

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `instanceUrl` | string | Yes | ServiceNow instance URL |
| `username` | string | Yes | ServiceNow username |
| `password` | string | Yes | ServiceNow password |
| `tableName` | string | Yes | Table name \(e.g., incident, task, sys_user\) |
| `sysId` | string | Yes | Record sys_id to delete |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
| `metadata` | json | Operation metadata including deleted sys_id |



## Notes

- Category: `tools`
- Type: `servicenow`
- Authentication: Supports OAuth 2.0 (recommended) via Sim Bot or Basic Auth with username/password
- Table Names: Common tables include `incident`, `task`, `sys_user`, `change_request`, `problem`, etc.
- Query Syntax: Use ServiceNow encoded query syntax (e.g., `active=true^priority=1`) for filtering records
- sys_id: Every ServiceNow record has a unique `sys_id` that is used to identify and reference records

2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default function Footer({ fullWidth = false }: FooterProps) {
{FOOTER_BLOCKS.map((block) => (
<Link
key={block}
href={`https://docs.sim.ai/blocks/${block.toLowerCase().replace(' ', '-')}`}
href={`https://docs.sim.ai/blocks/${block.toLowerCase().replaceAll(' ', '-')}`}
target='_blank'
rel='noopener noreferrer'
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'
Expand Down
14 changes: 11 additions & 3 deletions apps/sim/app/api/auth/oauth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export async function getOAuthToken(userId: string, providerId: string): Promise
accessToken: account.accessToken,
refreshToken: account.refreshToken,
accessTokenExpiresAt: account.accessTokenExpiresAt,
idToken: account.idToken,
})
.from(account)
.where(and(eq(account.userId, userId), eq(account.providerId, providerId)))
Expand Down Expand Up @@ -130,7 +131,9 @@ export async function getOAuthToken(userId: string, providerId: string): Promise

try {
// Use the existing refreshOAuthToken function
const refreshResult = await refreshOAuthToken(providerId, credential.refreshToken!)
// For ServiceNow, pass the instance URL (stored in idToken) for the token endpoint
const instanceUrl = providerId === 'servicenow' ? credential.idToken ?? undefined : undefined
const refreshResult = await refreshOAuthToken(providerId, credential.refreshToken!, instanceUrl)

if (!refreshResult) {
logger.error(`Failed to refresh token for user ${userId}, provider ${providerId}`, {
Expand Down Expand Up @@ -213,9 +216,12 @@ export async function refreshAccessTokenIfNeeded(
if (shouldRefresh) {
logger.info(`[${requestId}] Token expired, attempting to refresh for credential`)
try {
// For ServiceNow, pass the instance URL (stored in idToken) for the token endpoint
const instanceUrl = credential.providerId === 'servicenow' ? credential.idToken ?? undefined : undefined
const refreshedToken = await refreshOAuthToken(
credential.providerId,
credential.refreshToken!
credential.refreshToken!,
instanceUrl
)

if (!refreshedToken) {
Expand Down Expand Up @@ -287,7 +293,9 @@ export async function refreshTokenIfNeeded(
}

try {
const refreshResult = await refreshOAuthToken(credential.providerId, credential.refreshToken!)
// For ServiceNow, pass the instance URL (stored in idToken) for the token endpoint
const instanceUrl = credential.providerId === 'servicenow' ? credential.idToken ?? undefined : undefined
const refreshResult = await refreshOAuthToken(credential.providerId, credential.refreshToken!, instanceUrl)

if (!refreshResult) {
logger.error(`[${requestId}] Failed to refresh token for credential`)
Expand Down
166 changes: 166 additions & 0 deletions apps/sim/app/api/auth/oauth2/callback/servicenow/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { env } from '@/lib/core/config/env'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'

const logger = createLogger('ServiceNowCallback')

export const dynamic = 'force-dynamic'

export async function GET(request: NextRequest) {
const baseUrl = getBaseUrl()

try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.redirect(`${baseUrl}/workspace?error=unauthorized`)
}

const { searchParams } = request.nextUrl
const code = searchParams.get('code')
const state = searchParams.get('state')
const error = searchParams.get('error')
const errorDescription = searchParams.get('error_description')

// Handle OAuth errors from ServiceNow
if (error) {
logger.error('ServiceNow OAuth error:', { error, errorDescription })
return NextResponse.redirect(
`${baseUrl}/workspace?error=servicenow_auth_error&message=${encodeURIComponent(errorDescription || error)}`
)
}

const storedState = request.cookies.get('servicenow_oauth_state')?.value
const storedInstanceUrl = request.cookies.get('servicenow_instance_url')?.value

const clientId = env.SERVICENOW_CLIENT_ID
const clientSecret = env.SERVICENOW_CLIENT_SECRET

if (!clientId || !clientSecret) {
logger.error('ServiceNow credentials not configured')
return NextResponse.redirect(`${baseUrl}/workspace?error=servicenow_config_error`)
}

// Validate state parameter
if (!state || state !== storedState) {
logger.error('State mismatch in ServiceNow OAuth callback')
return NextResponse.redirect(`${baseUrl}/workspace?error=servicenow_state_mismatch`)
}

// Validate authorization code
if (!code) {
logger.error('No code received from ServiceNow')
return NextResponse.redirect(`${baseUrl}/workspace?error=servicenow_no_code`)
}

// Validate instance URL
if (!storedInstanceUrl) {
logger.error('No instance URL stored')
return NextResponse.redirect(`${baseUrl}/workspace?error=servicenow_no_instance`)
}

const redirectUri = `${baseUrl}/api/auth/oauth2/callback/servicenow`

// Exchange authorization code for access token
const tokenResponse = await fetch(`${storedInstanceUrl}/oauth_token.do`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri,
client_id: clientId,
client_secret: clientSecret,
}).toString(),
})

if (!tokenResponse.ok) {
const errorText = await tokenResponse.text()
logger.error('Failed to exchange code for token:', {
status: tokenResponse.status,
body: errorText,
})
return NextResponse.redirect(`${baseUrl}/workspace?error=servicenow_token_error`)
}

const tokenData = await tokenResponse.json()
const accessToken = tokenData.access_token
const refreshToken = tokenData.refresh_token
const expiresIn = tokenData.expires_in
// ServiceNow always grants 'useraccount' scope but returns empty string
const scope = tokenData.scope || 'useraccount'

logger.info('ServiceNow token exchange successful:', {
hasAccessToken: !!accessToken,
hasRefreshToken: !!refreshToken,
expiresIn,
})

if (!accessToken) {
logger.error('No access token in response')
return NextResponse.redirect(`${baseUrl}/workspace?error=servicenow_no_token`)
}

// Redirect to store endpoint with token data in cookies
const storeUrl = new URL(`${baseUrl}/api/auth/oauth2/servicenow/store`)

const response = NextResponse.redirect(storeUrl)

// Store token data in secure cookies for the store endpoint
response.cookies.set('servicenow_pending_token', accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60, // 1 minute
path: '/',
})

if (refreshToken) {
response.cookies.set('servicenow_pending_refresh_token', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60,
path: '/',
})
}

response.cookies.set('servicenow_pending_instance', storedInstanceUrl, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60,
path: '/',
})

response.cookies.set('servicenow_pending_scope', scope || '', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60,
path: '/',
})

if (expiresIn) {
response.cookies.set('servicenow_pending_expires_in', expiresIn.toString(), {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60,
path: '/',
})
}

// Clean up OAuth state cookies
response.cookies.delete('servicenow_oauth_state')
response.cookies.delete('servicenow_instance_url')

return response
} catch (error) {
logger.error('Error in ServiceNow OAuth callback:', error)
return NextResponse.redirect(`${baseUrl}/workspace?error=servicenow_callback_error`)
}
}
Loading
Loading