for critical issues
## Next steps
[Section titled “Next steps”](#next-steps)
* [Scopes and Permissions](/agent-auth/authentication/scopes-permissions) - Managing OAuth scopes
* [Multi-Provider Authentication](/agent-auth/authentication/multi-provider) - Managing multiple connections
---
# DOCUMENT BOUNDARY
---
# Auth patterns
> Learn the supported auth types, provider payload fields, and auth pattern configuration used by bring your own provider.
Auth patterns define how Scalekit should authenticate to the upstream API.
Use this page to choose an auth type and build the provider payload for a custom provider.
## Choose an auth type
[Section titled “Choose an auth type”](#choose-an-auth-type)
Bring your own provider supports these auth types:
* OAUTH
* BASIC
* BEARER
* API\_KEY
Use OAUTH when the upstream API requires a user authorization flow and token exchange. Use BASIC, BEARER, or API\_KEY when the upstream API accepts static credentials or long-lived tokens that Scalekit can attach during proxy calls.
## Understand the provider payload
[Section titled “Understand the provider payload”](#understand-the-provider-payload)
The provider payload uses these common top-level fields:
* `display_name`: Human-readable name for the custom provider
* `description`: Short description of what the provider connects to
* `auth_patterns`: Authentication options supported by the provider
* `proxy_url`: Base URL the proxy should call for the upstream API (mandatory)
* `proxy_enabled`: Whether the proxy is enabled for the provider (mandatory, should be true)
`proxy_url` can also include templated fields when the upstream API requires account-specific values, for example `https://{{domain}}/api`.
## Understand auth pattern fields
[Section titled “Understand auth pattern fields”](#understand-auth-pattern-fields)
Within `auth_patterns`, the most common fields are:
* `type`: The auth type, such as OAUTH, BASIC, BEARER, or API\_KEY
* `display_name`: Label shown for that auth option
* `description`: Short explanation of the auth method
* `fields`: Inputs collected for static auth providers such as BASIC, BEARER, and API\_KEY. These usually store values such as `username`, `password`, `token`, `api_key`, `domain`, or `version`.
* `account_fields`: Inputs collected for OAUTH providers when account-scoped values are needed. This is typically used for values tied to a connected account, such as named path parameters.
* `oauth_config`: OAuth-specific configuration, such as authorize and token endpoints
* `auth_header_key_override`: Custom header name when the upstream does not use `Authorization`. For example, some APIs expect auth in a header such as `X-API-Key` instead of the standard `Authorization` header.
* `auth_field_mutations`: Value transformations applied before the credential is sent. This is useful when the upstream expects a prefix, suffix, or default companion value, such as adding a token prefix or setting a fallback password value for Basic auth.
These fields control how Scalekit collects credentials and applies them during proxy calls.
## Example JSON payloads
[Section titled “Example JSON payloads”](#example-json-payloads)
Use a payload like the following based on the auth pattern you need. In the management flow, you can pass these JSON bodies into the appropriate create or update request.
* OAuth
```json
1
{
2
"display_name": "My Asana",
3
"description": "Connect to Asana. Manage tasks, projects, teams, and workflow automation",
4
"auth_patterns": [
5
{
6
"type": "OAUTH",
7
"display_name": "OAuth 2.0",
8
"description": "Authenticate with Asana using OAuth 2.0 for comprehensive project management",
9
"fields": [],
10
"oauth_config": {
11
"authorize_uri": "https://app.asana.com/-/oauth_authorize",
12
"token_uri": "https://app.asana.com/-/oauth_token",
13
"user_info_uri": "https://app.asana.com/api/1.0/users/me",
14
"available_scopes": [
15
{
16
"scope": "profile",
17
"display_name": "Profile",
18
"description": "Access user profile information",
19
"required": true
20
},
21
{
22
"scope": "email",
23
"display_name": "Email",
24
"description": "Access user email address",
25
"required": true
26
}
27
]
28
}
29
}
30
],
31
"proxy_url": "https://app.asana.com/api",
32
"proxy_enabled": true
33
}
```
* Bearer
```json
1
{
2
"display_name": "My Bearer Token Provider",
3
"description": "Connect to an API that accepts a static bearer token",
4
"auth_patterns": [
5
{
6
"type": "BEARER",
7
"display_name": "Bearer Token",
8
"description": "Authenticate with a static bearer token",
9
"fields": [
10
{
11
"field_name": "token",
12
"label": "Bearer Token",
13
"input_type": "password",
14
"hint": "Your long-lived bearer token",
15
"required": true
16
}
17
]
18
}
19
],
20
"proxy_url": "https://api.example.com",
21
"proxy_enabled": true
22
}
```
* Basic
```json
1
{
2
"display_name": "My Freshdesk",
3
"description": "Connect to Freshdesk. Manage tickets, contacts, companies, and customer support workflows",
4
"auth_patterns": [
5
{
6
"type": "BASIC",
7
"display_name": "Basic Auth",
8
"description": "Authenticate with Freshdesk using Basic Auth with username and password for comprehensive helpdesk management",
9
"fields": [
10
{
11
"field_name": "domain",
12
"label": "Freshdesk Domain",
13
"input_type": "text",
14
"hint": "Your Freshdesk domain (e.g., yourcompany.freshdesk.com)",
15
"required": true
16
},
17
{
18
"field_name": "username",
19
"label": "API Key",
20
"input_type": "text",
21
"hint": "Your Freshdesk API Key",
22
"required": true
23
}
24
]
25
}
26
],
27
"proxy_url": "https://{{domain}}/api",
28
"proxy_enabled": true
29
}
```
* API Key
```json
1
{
2
"display_name": "My Attention",
3
"description": "Connect to Attention for AI insights, conversations, teams, and workflows",
4
"auth_patterns": [
5
{
6
"type": "API_KEY",
7
"display_name": "API Key",
8
"description": "Authenticate with Attention using an API Key",
9
"fields": [
10
{
11
"field_name": "api_key",
12
"label": "Integration Token",
13
"input_type": "password",
14
"hint": "Your Attention API Key",
15
"required": true
16
}
17
]
18
}
19
],
20
"proxy_url": "https://api.attention.tech",
21
"proxy_enabled": true
22
}
```
## Review the final payload carefully
[Section titled “Review the final payload carefully”](#review-the-final-payload-carefully)
When you review the final payload, pay close attention to:
* `display_name` and `description`
* The selected auth `type`
* Required `fields` and `account_fields`
* OAuth endpoints and scopes, if the provider uses OAuth
* `proxy_url`
Use the payload body from this page in the create and update requests on the [Managing providers](/agent-auth/bring-your-own-provider/managing-providers) page.
---
# DOCUMENT BOUNDARY
---
# Managing providers
> Use the recommended skill to create, review, update, promote, and delete bring your own provider definitions.
Use this page to create, list, update, promote, and delete custom providers in Scalekit.
Create providers in `Dev` first, validate the definition, and then use the same definition for updates or Production rollout.
Recommended approach
The recommended way to manage providers is with the [`sk-actions-custom-provider` skill](https://github.com/scalekit-inc/skills/tree/main/skills/agent-auth/sk-actions-custom-provider). It keeps payload generation, review, and promotion consistent across Dev and Production.
Use it to:
* Infer the provider auth type
* Generate the provider payload
* Review existing providers before create or update
* Show diffs before updates
* Move provider definitions from Dev to Production
* Prepare the delete curl for a provider
Whenever the skill shows you the final provider payload, review the values carefully before approving or running the next step.
If you prefer to manage custom providers with the APIs directly, use the `curl` commands on this page and the JSON bodies from [Auth patterns](/agent-auth/bring-your-own-provider/auth-types-and-patterns).
Before using the `curl` examples on this page, make sure:
* `SCALEKIT_ENVIRONMENT_URL` points to the Scalekit environment where you want to manage the provider
* `env_access_token` contains a valid environment access token for that environment
## Create a provider
[Section titled “Create a provider”](#create-a-provider)
Create the provider in `Dev` first. Share the provider name, Scalekit credentials, API docs, auth docs, and base API URL if you already know it. The skill will infer the auth pattern, generate the provider payload, and help you create it.
Use one of the JSON payloads from [Auth patterns](/agent-auth/bring-your-own-provider/auth-types-and-patterns) as the request body:
```bash
1
curl --location "$SCALEKIT_ENVIRONMENT_URL/api/v1/custom-providers" \
2
--header "Authorization: Bearer $env_access_token" \
3
--header "Content-Type: application/json" \
4
--data '{...}'
```
After the provider is created, create a connection in the Scalekit Dashboard and continue with the standard Agent Auth flow.
## List providers
[Section titled “List providers”](#list-providers)
List existing providers before you create or update one. This helps you confirm whether the provider already exists and whether you should create a new provider or update an existing one.
```bash
1
curl --location "$SCALEKIT_ENVIRONMENT_URL/api/v1/providers?filter.provider_type=CUSTOM&page_size=1000" \
2
--header "Authorization: Bearer $env_access_token"
```
## Update a provider
[Section titled “Update a provider”](#update-a-provider)
When a provider changes, use the skill to compare the current provider with the proposed payload. Review auth fields, scopes, and proxy settings carefully before applying the update, because these values affect how future authorization and proxy calls behave.
Use the [List providers](#list-providers) API to get the provider `identifier` from the response before sending the update request.
Use the updated JSON body from [Auth patterns](/agent-auth/bring-your-own-provider/auth-types-and-patterns) and the provider `identifier` in the update request:
```bash
1
curl --location --request PUT "$SCALEKIT_ENVIRONMENT_URL/api/v1/custom-providers/$PROVIDER_IDENTIFIER" \
2
--header "Authorization: Bearer $env_access_token" \
3
--header "Content-Type: application/json" \
4
--data '{...}'
```
## Move a provider from Dev to Production
[Section titled “Move a provider from Dev to Production”](#move-a-provider-from-dev-to-production)
Use the `Dev` provider as the source of truth. The skill can locate the matching provider in `Dev`, compare it with `Production`, and prepare the correct action from there.
In practice, this means:
* fetch the provider definition from `Dev`
* review the payload
* create it in `Production` if it does not exist
* update it in `Production` if it already exists
Use the same JSON body from [Auth patterns](/agent-auth/bring-your-own-provider/auth-types-and-patterns) for the Production create or update request. This keeps the provider definition consistent between environments.
## Delete a provider
[Section titled “Delete a provider”](#delete-a-provider)
To delete a provider, resolve the correct provider identifier first. If the provider is still in use, remove the related connections or connected accounts before retrying the delete flow.
Use the [List providers](#list-providers) API to get the provider `identifier` from the response before sending the delete request.
```bash
1
curl --location --request DELETE "$SCALEKIT_ENVIRONMENT_URL/api/v1/custom-providers/$PROVIDER_IDENTIFIER" \
2
--header "Authorization: Bearer $env_access_token"
```
---
# DOCUMENT BOUNDARY
---
# Bring your own provider
> Add custom providers to Agent Auth and extend provider coverage while keeping authentication and authorization in Scalekit.
Bring your own provider lets you add custom providers to Agent Auth when the API you need is not available as a built-in provider.
Use bring your own provider to support unsupported SaaS APIs, partner systems, and internal APIs while keeping authentication, authorization, and secure API access in Scalekit.
Once the provider is created, you use the same Agent Auth flow as other providers: create a connection, create or fetch a connected account, authorize the user, and call the upstream API through Tool Proxy.
Custom providers appear alongside built-in providers when you create a connection in Scalekit:

## Why use bring your own provider
[Section titled “Why use bring your own provider”](#why-use-bring-your-own-provider)
Bring your own provider lets you:
* Extend Agent Auth beyond the built-in provider catalog without inventing a separate auth stack
* Bring unsupported SaaS APIs, partner systems, and internal APIs into the same secure access model
* Reuse connections, connected accounts, and user authorization instead of building one-off auth plumbing
* Keep credential handling, authorization, and governed API access centralized in Scalekit
* Move from provider definition to live upstream API calls through Tool Proxy using the same runtime model as other integrations
Note
Bring your own provider is for proxy-only connectors.
## How bring your own provider works
[Section titled “How bring your own provider works”](#how-bring-your-own-provider-works)
Bring your own provider uses the same Agent Auth model as built-in providers:
1. Create a provider definition
2. Create a connection in Scalekit Dashboard
3. Create a connected account and authorize the user
4. Use Tool Proxy to call the upstream API
Creating the provider defines how Scalekit should authenticate to the upstream API. After that, connections, connected accounts, user authorization, and Tool Proxy work the same way as they do for built-in providers.
---
# DOCUMENT BOUNDARY
---
# Use Tool Proxy
> Use Tool Proxy with your provider after creating a connection, authorizing a user, and setting up a connected account.
Use this page to call a custom provider through Tool Proxy after the provider, connection, and connected account are set up.
The provider definition controls how Scalekit authenticates the upstream API. Your application still uses a connection, a connected account, user authorization, and `actions.request(...)`.
Bring your own provider does not introduce a separate runtime model. You still use the standard Agent Auth flow:
* Create a connection for the provider in Scalekit Dashboard
* Create or fetch the connected account for the user
* Authorize the user if the connected account is not active
* Call the upstream API through Tool Proxy
Tool Proxy uses the connected account context to inject the correct authentication details before routing the request to the upstream API.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Make sure:
* The provider exists and is configured with the right [auth pattern](/agent-auth/bring-your-own-provider/auth-types-and-patterns)
* A [connection](/agent-auth/connections) is configured for the provider
* The [connected account](/agent-auth/connected-accounts) exists
* The user has completed [authorization](/agent-auth/tools/authorize)
Once these pieces are in place, you can call the upstream API through Tool Proxy.
In the request examples below, `path` is relative to the provider `proxy_url`. `connectionName` must match the connection you created, and `identifier` must match the connected account you want to use for the request.
After you create the provider, create a connection for it in the Scalekit Dashboard:

After the user completes authorization, the connected account appears in the Connected Accounts tab and is ready for proxy calls:

## Proxy API calls
[Section titled “Proxy API calls”](#proxy-api-calls)
* Node.js
```typescript
1
import { ScalekitClient } from '@scalekit-sdk/node';
2
import 'dotenv/config';
3
4
const connectionName = 'your-provider-connection'; // get your connection name from connection configurations
5
const identifier = 'user_123'; // your unique user identifier
6
7
// Get your credentials from app.scalekit.com → Developers → Settings → API Credentials
8
const scalekit = new ScalekitClient(
9
process.env.SCALEKIT_ENV_URL,
10
process.env.SCALEKIT_CLIENT_ID,
11
process.env.SCALEKIT_CLIENT_SECRET
12
);
13
const actions = scalekit.actions;
14
15
// Authenticate the user
16
const { link } = await actions.getAuthorizationLink({
17
connectionName,
18
identifier,
19
});
20
console.log('Authorize provider:', link);
21
process.stdout.write('Press Enter after authorizing...');
22
await new Promise(r => process.stdin.once('data', r));
23
24
// Make a request via Scalekit proxy
25
const result = await actions.request({
26
connectionName,
27
identifier,
28
path: '/v1/customers',
29
method: 'GET',
30
});
31
console.log(result);
```
* Python
```python
1
import scalekit.client, os
2
from dotenv import load_dotenv
3
load_dotenv()
4
5
connection_name = "your-provider-connection" # get your connection name from connection configurations
6
identifier = "user_123" # your unique user identifier
7
8
# Get your credentials from app.scalekit.com → Developers → Settings → API Credentials
9
scalekit_client = scalekit.client.ScalekitClient(
10
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
11
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
12
env_url=os.getenv("SCALEKIT_ENV_URL"),
13
)
14
actions = scalekit_client.actions
15
16
# Authenticate the user
17
link_response = actions.get_authorization_link(
18
connection_name=connection_name,
19
identifier=identifier
20
)
21
# present this link to your user for authorization, or click it yourself for testing
22
print("Authorize provider:", link_response.link)
23
input("Press Enter after authorizing...")
24
25
# Make a request via Scalekit proxy
26
result = actions.request(
27
connection_name=connection_name,
28
identifier=identifier,
29
path="/v1/customers",
30
method="GET"
31
)
32
print(result)
```
The request shape stays the same regardless of whether the provider uses OAUTH, BASIC, BEARER, or API\_KEY. The provider definition controls how Scalekit authenticates the upstream call.
---
# DOCUMENT BOUNDARY
---
# Code samples
> Code samples of AI agents using Scalekit along with LangChain, Google ADK, and direct integrations
### [Connect LangChain agents to Gmail](https://github.com/scalekit-inc/sample-langchain-agent)
[Securely connect a LangChain agent to Gmail using Scalekit for authentication. Python example for tool authorization.](https://github.com/scalekit-inc/sample-langchain-agent)
### [Connect Google GenAI agents to Gmail](https://github.com/scalekit-inc/google-adk-agent-example)
[Build a Google ADK agent that securely accesses Gmail tools. Python example demonstrating Scalekit auth integration.](https://github.com/scalekit-inc/google-adk-agent-example)
### [Connect agents to Slack tools](https://github.com/scalekit-inc/python-connect-demos/tree/main/direct)
[Authorize Python agents to use Slack tools with Scalekit. Direct integration example for secure tool access.](https://github.com/scalekit-inc/python-connect-demos/tree/main/direct)
---
# DOCUMENT BOUNDARY
---
# Connected accounts
> Learn how to manage connected accounts in Agent Auth, including user authentication, authorization status, and account lifecycle management.
Connected accounts in Agent Auth represent individual user or organization connections to third-party providers. They contain the authentication state, tokens, and permissions needed to execute tools on behalf of a specific identifier (user\_id, org\_id, or custom identifier).
## What are connected accounts?
[Section titled “What are connected accounts?”](#what-are-connected-accounts)
Connected accounts are the runtime instances that link your users to their third-party application accounts. Each connected account:
* **Links to a connection**: Uses a pre-configured connection for authentication
* **Has a unique identifier**: Associated with a user\_id, org\_id, or custom identifier
* **Maintains auth state**: Tracks whether the user has completed authentication
* **Stores tokens**: Securely holds access tokens and refresh tokens
* **Manages permissions**: Tracks granted scopes and permissions
## Connected account lifecycle
[Section titled “Connected account lifecycle”](#connected-account-lifecycle)
Connected accounts go through several states during their lifecycle:
### Account states
[Section titled “Account states”](#account-states)
1. **Pending**: Account created but user hasn’t completed authentication
2. **Active**: User has authenticated and tokens are valid
3. **Expired**: Tokens have expired and need refresh
4. **Revoked**: User has revoked access to the application
5. **Error**: Account has authentication or configuration errors
6. **Suspended**: Account temporarily disabled
### State transitions
[Section titled “State transitions”](#state-transitions)
## Creating connected accounts
[Section titled “Creating connected accounts”](#creating-connected-accounts)
### Using the dashboard
[Section titled “Using the dashboard”](#using-the-dashboard)
1. **Navigate to connected accounts** in your Agent Auth dashboard
2. **Click create account** to start the process
3. **Select connection** to use for authentication
4. **Enter identifier** (user\_id, email, or custom identifier)
5. **Configure settings** such as scopes and permissions
6. **Generate auth URL** for the user to complete authentication
7. **Monitor status** until user completes the flow
### Using the API
[Section titled “Using the API”](#using-the-api)
Create connected accounts programmatically:
* Python
```python
1
# actions = scalekit_client.actions (initialize ScalekitClient first — see quickstart)
2
response = actions.get_or_create_connected_account(
3
connection_name="gmail",
4
identifier="user_123"
5
)
6
connected_account = response.connected_account
7
print(f"Connected account: {connected_account.id}, status: {connected_account.status}")
```
* Node.js
```typescript
1
// const actions = new ScalekitClient(ENV_URL, CLIENT_ID, CLIENT_SECRET).actions
2
const response = await actions.getOrCreateConnectedAccount({
3
connectionName: 'gmail',
4
identifier: 'user_123',
5
});
6
7
const connectedAccount = response.connectedAccount;
8
console.log('Connected account:', connectedAccount?.id, 'status:', connectedAccount?.status);
```
## Authentication flow
[Section titled “Authentication flow”](#authentication-flow)
### OAuth 2.0 flow
[Section titled “OAuth 2.0 flow”](#oauth-20-flow)
For OAuth connections, connected accounts follow the standard OAuth flow:
1. **Create connected account** with pending status
2. **Generate authorization URL** for the user
3. **User completes OAuth flow** with the third-party provider
4. **Provider redirects back** with authorization code
5. **Exchange code for tokens** and update account status
6. **Account becomes active** and ready for tool execution
### Authorization URL generation
[Section titled “Authorization URL generation”](#authorization-url-generation)
Generate URLs for users to complete authentication:
* Python
```python
1
link_response = actions.get_authorization_link(
2
connection_name="gmail",
3
identifier="user_123"
4
)
5
print(f"Authorization URL: {link_response.link}")
6
# Redirect the user to link_response.link to complete OAuth
```
* Node.js
```typescript
1
const linkResponse = await actions.getAuthorizationLink({
2
connectionName: 'gmail',
3
identifier: 'user_123',
4
});
5
6
console.log('Authorization URL:', linkResponse.link);
7
// Redirect the user to linkResponse.link to complete OAuth
```
### Handling callbacks
[Section titled “Handling callbacks”](#handling-callbacks)
Scalekit handles the OAuth callback automatically. Once the user completes the authorization flow, Scalekit exchanges the code for tokens and updates the connected account status to `ACTIVE`.
## Managing connected accounts
[Section titled “Managing connected accounts”](#managing-connected-accounts)
### Account information
[Section titled “Account information”](#account-information)
Retrieve connected account details and OAuth tokens:
* Python
```python
1
response = actions.get_connected_account(
2
connection_name="gmail",
3
identifier="user_123"
4
)
5
connected_account = response.connected_account
6
7
# Extract OAuth tokens from authorization details
8
tokens = connected_account.authorization_details["oauth_token"]
9
access_token = tokens["access_token"]
10
refresh_token = tokens["refresh_token"]
11
12
print(f"Account ID: {connected_account.id}")
13
print(f"Status: {connected_account.status}")
```
* Node.js
```typescript
1
const accountResponse = await actions.getConnectedAccount({
2
connectionName: 'gmail',
3
identifier: 'user_123',
4
});
5
6
const connectedAccount = accountResponse?.connectedAccount;
7
const authDetails = connectedAccount?.authorizationDetails;
8
9
// Extract OAuth tokens from authorization details
10
const accessToken = (authDetails && authDetails.details?.case === 'oauthToken')
11
? authDetails.details.value?.accessToken
12
: undefined;
13
const refreshToken = (authDetails && authDetails.details?.case === 'oauthToken')
14
? authDetails.details.value?.refreshToken
15
: undefined;
16
17
console.log('Account ID:', connectedAccount?.id);
18
console.log('Status:', connectedAccount?.status);
```
### Token management
[Section titled “Token management”](#token-management)
Connected accounts automatically handle token lifecycle:
**Automatic token refresh:**
* Tokens are refreshed automatically before expiration
* Refresh happens transparently during tool execution
* Failed refresh attempts update account status to expired
**Manual token refresh:**
There is no SDK method to manually trigger a token refresh. If a connected account’s status is `EXPIRED` or `ERROR`, generate a new authorization link and prompt the user to re-authorize:
* Python
```python
1
response = actions.get_or_create_connected_account(
2
connection_name="gmail",
3
identifier="user_123"
4
)
5
connected_account = response.connected_account
6
7
if connected_account.status != "ACTIVE":
8
# Re-authorize the user to refresh their tokens
9
link_response = actions.get_authorization_link(
10
connection_name="gmail",
11
identifier="user_123"
12
)
13
print(f"Re-authorization required. Send user to: {link_response.link}")
```
* Node.js
```typescript
1
const response = await actions.getOrCreateConnectedAccount({
2
connectionName: 'gmail',
3
identifier: 'user_123',
4
});
5
6
const connectedAccount = response.connectedAccount;
7
8
if (connectedAccount?.status !== 'ACTIVE') {
9
// Re-authorize the user to refresh their tokens
10
const linkResponse = await actions.getAuthorizationLink({
11
connectionName: 'gmail',
12
identifier: 'user_123',
13
});
14
console.log('Re-authorization required. Send user to:', linkResponse.link);
15
}
```
### Account status monitoring
[Section titled “Account status monitoring”](#account-status-monitoring)
Monitor account authentication status:
* Python
```python
1
response = actions.get_connected_account(
2
connection_name="gmail",
3
identifier="user_123"
4
)
5
connected_account = response.connected_account
6
7
# Possible status values:
8
# - PENDING: Waiting for user authentication
9
# - ACTIVE: Authenticated and ready
10
# - EXPIRED: Tokens expired, needs re-authorization
11
# - REVOKED: User revoked access
12
# - ERROR: Authentication error
13
print(f"Account status: {connected_account.status}")
```
* Node.js
```typescript
1
const accountResponse = await actions.getConnectedAccount({
2
connectionName: 'gmail',
3
identifier: 'user_123',
4
});
5
6
const connectedAccount = accountResponse?.connectedAccount;
7
8
// Possible status values:
9
// - PENDING: Waiting for user authentication
10
// - ACTIVE: Authenticated and ready
11
// - EXPIRED: Tokens expired, needs re-authorization
12
// - REVOKED: User revoked access
13
// - ERROR: Authentication error
14
console.log('Account status:', connectedAccount?.status);
```
## Account permissions and scopes
[Section titled “Account permissions and scopes”](#account-permissions-and-scopes)
Scopes define what actions a connected account can perform on a user’s behalf. Understanding how scopes are configured and updated is critical to building reliable agent integrations.
### Scopes are set at the connection level
[Section titled “Scopes are set at the connection level”](#scopes-are-set-at-the-connection-level)
Scopes are configured at the **connection level**, not at the individual connected account level. When a user completes the OAuth authorization flow for a connected account, they approve exactly the scopes defined on that connection.
**Scopes are read-only after a connected account is created.** There is no API or SDK method to modify the granted scopes on an existing connected account after the user has completed authentication.
### Add or change scopes
[Section titled “Add or change scopes”](#add-or-change-scopes)
To request additional scopes for an existing connected account:
1. **Update the connection configuration** — In the Scalekit dashboard, navigate to the connection and add the new scopes.
2. **Generate a new magic link** — Use the Scalekit dashboard or API to create a new authorization link for the user.
3. **User approves the updated consent screen** — The user visits the link and approves the expanded OAuth consent screen with the new scopes.
4. **Connected account is updated** — After the user approves, Scalekit updates the connected account with the new token set.
The user must go through the OAuth flow again whenever scopes change. There is no way to silently add scopes on their behalf.
### Account and connector status values
[Section titled “Account and connector status values”](#account-and-connector-status-values)
When working with connected accounts, you may encounter the following enum values from the Scalekit platform:
**Connector status**
| Value | Description |
| --------------------------- | ----------------------------------------------------- |
| `CONNECTOR_STATUS_ACTIVE` | Connector is configured and operational |
| `CONNECTOR_STATUS_INACTIVE` | Connector is configured but not active |
| `CONNECTOR_STATUS_PENDING` | Connector setup is incomplete |
| `CONNECTOR_STATUS_ERROR` | Connector has a configuration or authentication error |
**Connector type**
| Value | Description |
| --------------------------- | ------------------------------------------------- |
| `CONNECTOR_TYPE_OAUTH2` | OAuth 2.0 connection (e.g., Gmail, Slack, GitHub) |
| `CONNECTOR_TYPE_API_KEY` | API key-based connection (e.g., Zendesk, HubSpot) |
| `CONNECTOR_TYPE_BASIC_AUTH` | Username and password connection |
These values are returned in API responses when listing or inspecting connections and connected accounts.
## Account metadata and settings
[Section titled “Account metadata and settings”](#account-metadata-and-settings)
### Custom metadata
[Section titled “Custom metadata”](#custom-metadata)
Custom metadata is managed via the Scalekit dashboard.
## Bulk operations
[Section titled “Bulk operations”](#bulk-operations)
### Managing multiple accounts
[Section titled “Managing multiple accounts”](#managing-multiple-accounts)
The Python SDK supports read-only retrieval via `actions.get_connected_account` (Python) / `actions.getConnectedAccount` (Node.js). Use `actions.get_or_create_connected_account` when you need create-or-retrieve semantics. Bulk list and delete operations (`actions.listConnectedAccounts`, `actions.deleteConnectedAccount`) are available in the Node.js SDK, or via the direct API and dashboard.
* Python
```python
1
# Get or create a connected account by identifier
2
response = actions.get_or_create_connected_account(
3
connection_name="gmail",
4
identifier="user_123"
5
)
6
connected_account = response.connected_account
7
print(f"Account: {connected_account.id}, Status: {connected_account.status}")
```
* Node.js
```typescript
1
// List connected accounts
2
const listResponse = await actions.listConnectedAccounts({
3
connectionName: 'gmail',
4
});
5
console.log('Connected accounts:', listResponse);
6
7
// Delete a connected account
8
await actions.deleteConnectedAccount({
9
connectionName: 'gmail',
10
identifier: 'user_123',
11
});
12
console.log('Connected account deleted');
```
## Error handling
[Section titled “Error handling”](#error-handling)
### Common errors
[Section titled “Common errors”](#common-errors)
Handle common connected account errors:
* Python
```python
1
try:
2
response = actions.get_connected_account(
3
connection_name="gmail",
4
identifier="user_123"
5
)
6
connected_account = response.connected_account
7
except Exception as e:
8
print(f"Error retrieving connected account: {e}")
9
# Check connected_account.status for EXPIRED, REVOKED, or ERROR states
10
# and prompt the user to re-authorize if needed
```
* Node.js
```typescript
1
try {
2
const accountResponse = await actions.getConnectedAccount({
3
connectionName: 'gmail',
4
identifier: 'user_123',
5
});
6
const connectedAccount = accountResponse?.connectedAccount;
7
console.log('Account status:', connectedAccount?.status);
8
} catch (error) {
9
console.error('Error retrieving connected account:', error);
10
// Check connectedAccount?.status for EXPIRED, REVOKED, or ERROR states
11
// and prompt the user to re-authorize if needed
12
}
```
### Error recovery
[Section titled “Error recovery”](#error-recovery)
Implement error recovery strategies:
1. **Detect error** - Monitor account status and API responses
2. **Classify error** - Determine if error is recoverable
3. **Attempt recovery** - Try token refresh or re-authentication
4. **Notify user** - Inform user if manual action is required
5. **Update status** - Update account status based on recovery result
## Security considerations
[Section titled “Security considerations”](#security-considerations)
### Token security
[Section titled “Token security”](#token-security)
Protect user tokens and credentials:
* **Encryption**: All tokens are encrypted at rest and in transit
* **Token rotation**: Implement regular token rotation
* **Access logging**: Log all token access and usage
* **Secure storage**: Use secure storage mechanisms for tokens
### Permission management
[Section titled “Permission management”](#permission-management)
Follow principle of least privilege:
* **Minimal scopes**: Request only necessary permissions
* **Scope validation**: Verify permissions before tool execution
* **Regular audit**: Review granted permissions regularly
* **User consent**: Ensure users understand granted permissions
### Account isolation
[Section titled “Account isolation”](#account-isolation)
Ensure proper account isolation:
* **Tenant isolation**: Separate accounts by tenant/organization
* **User isolation**: Prevent cross-user data access
* **Connection isolation**: Separate different connection types
* **Audit trail**: Maintain detailed audit logs
## Monitoring and analytics
[Section titled “Monitoring and analytics”](#monitoring-and-analytics)
### Account health monitoring
[Section titled “Account health monitoring”](#account-health-monitoring)
Monitor the status of connected accounts by calling `actions.get_connected_account` (Python) or `actions.getConnectedAccount` (Node.js) and inspecting the `status` field. Accounts in a non-`ACTIVE` state may require re-authorization.
### Usage analytics
[Section titled “Usage analytics”](#usage-analytics)
Track usage patterns and tool execution results through the Scalekit dashboard. SDK methods for usage analytics are not currently available.
## Best practices
[Section titled “Best practices”](#best-practices)
### Account lifecycle management
[Section titled “Account lifecycle management”](#account-lifecycle-management)
* **Regular cleanup**: Remove unused or expired accounts
* **Status monitoring**: Monitor account status changes
* **Proactive refresh**: Refresh tokens before expiration
* **User notifications**: Notify users of authentication issues
### Performance optimization
[Section titled “Performance optimization”](#performance-optimization)
* **Connection pooling**: Reuse connections efficiently
* **Token caching**: Cache tokens appropriately
* **Batch operations**: Use bulk operations when possible
* **Async processing**: Handle authentication flows asynchronously
### User experience
[Section titled “User experience”](#user-experience)
* **Clear error messages**: Provide helpful error messages to users
* **Seamless re-auth**: Make re-authentication flows smooth
* **Status visibility**: Show users their connection status
* **Easy revocation**: Allow users to easily revoke access
## Testing connected accounts
[Section titled “Testing connected accounts”](#testing-connected-accounts)
### Development testing
[Section titled “Development testing”](#development-testing)
Test connected accounts in development:
* Python
```python
1
# Create or retrieve a test connected account
2
response = actions.get_or_create_connected_account(
3
connection_name="gmail",
4
identifier="test_user"
5
)
6
test_account = response.connected_account
7
print(f"Test account: {test_account.id}, status: {test_account.status}")
```
* Node.js
```typescript
1
// Create or retrieve a test connected account
2
const response = await actions.getOrCreateConnectedAccount({
3
connectionName: 'gmail',
4
identifier: 'test_user',
5
});
6
7
const testAccount = response.connectedAccount;
8
console.log('Test account:', testAccount?.id, 'status:', testAccount?.status);
```
### Integration testing
[Section titled “Integration testing”](#integration-testing)
Test authentication flows:
1. **Create test connection** with test credentials
2. **Create connected account** with test identifier
3. **Generate auth URL** and complete OAuth flow
4. **Verify account status** becomes active
5. **Test tool execution** with the account
6. **Test token refresh** and error scenarios
Next, learn how to [authorize a user](/agent-auth/tools/authorize) so connected accounts can complete authentication before tool execution.
---
# DOCUMENT BOUNDARY
---
# Connections
> Learn how to configure and manage connections in Agent Auth to enable authentication and tool execution with third-party providers.
A **connection** is a configured integration between Scalekit and a third-party provider. It holds the credentials and settings Scalekit needs to interact with that provider’s API on behalf of your users — OAuth client secrets, API keys, scopes, and so on.
You create one connection per provider in the Scalekit Dashboard. Once active, it can be shared across all your users.
## Connection types
[Section titled “Connection types”](#connection-types)
| Type | When to use |
| ---------------- | --------------------------------------------------------------------------------- |
| **OAuth 2.0** | Providers like Notion, Gmail, Slack, GitHub that use user-delegated authorization |
| **API Key** | Providers like Exa, HarvestAPI, Snowflake that authenticate with a static key |
| **Bearer token** | Providers that accept a long-lived bearer token |
| **Basic auth** | Providers that use username + password authentication |
## Creating a connection
[Section titled “Creating a connection”](#creating-a-connection)
1. Open the **Connections** section in the [Scalekit Dashboard](https://app.scalekit.com)
2. Click **Add connection** and select a provider
3. Enter the required credentials (OAuth client ID/secret, API key, etc.)
4. Save — the connection is now available for use
For a step-by-step example, see how to set up a [Gmail connection](/reference/agent-connectors/gmail/).
Next, learn how to create and manage [Connected accounts](/agent-auth/connected-accounts) that use these connections to authenticate and execute tools for your users.
---
# DOCUMENT BOUNDARY
---
# Agno
> Learn how to use CrewAI with Agent Auth.
# Agno
[Section titled “Agno”](#agno)
Agno is a framework for building AI agents. It provides a set of tools and abstractions for building AI agents.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# Anthropic
> Learn how to enable tool calling with Agent Auth on top of Anthropic's models.
# Anthropic
[Section titled “Anthropic”](#anthropic)
Anthropic is a framework for building AI agents. It provides a set of tools and abstractions for building AI agents.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# Google ADK
> Build a Google ADK agent that authenticates users with Gmail and executes tools on their behalf using Scalekit Agent Auth.
Build a Gmail-powered AI agent using Google ADK that authenticates users via OAuth 2.0 and fetches their last 5 unread emails using Scalekit Agent Auth. Visit the [Google ADK quickstart guide](https://google.github.io/adk-docs/get-started/quickstart/) to set up Google ADK before starting.
[Full code on GitHub ](https://github.com/scalekit-inc/google-adk-agent-example)
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Before you start, ensure you have:
* **Scalekit account and API credentials** — Get your Client ID and Client Secret from the [Scalekit dashboard](https://app.scalekit.com)
* **Google API Key** — Obtain from [Google AI Studio](https://aistudio.google.com)
* **Gmail account** — For testing the OAuth authentication flow
1. ### Set up your environment
[Section titled “Set up your environment”](#set-up-your-environment)
Install the Scalekit Python SDK and Google ADK, then initialize the client. Get your credentials from **Developers → Settings → API Credentials** in the [dashboard](https://app.scalekit.com).
```sh
pip install scalekit-sdk-python google-adk
```
```python
import scalekit.client
import os
scalekit_client = scalekit.client.ScalekitClient(
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit_client.actions
```
2. ### Create a connected account
[Section titled “Create a connected account”](#create-a-connected-account)
Authorize a user to access their Gmail account by creating a connected account. This represents the user’s connection to their Gmail account:
```python
1
# Create a connected account for user if it doesn't exist already
2
response = actions.get_or_create_connected_account(
3
connection_name="GMAIL",
4
identifier="user_123"
5
)
6
connected_account = response.connected_account
7
8
print(f'Connected account created: {connected_account.id}')
```
3. ### Authenticate the user
[Section titled “Authenticate the user”](#authenticate-the-user)
For Scalekit to execute tools on behalf of the user, the user must grant authorization to access their Gmail account. Scalekit automatically handles the entire OAuth workflow, including token refresh.
```python
1
# Check if the user needs to authorize their Gmail connection
2
# This handles both new users and cases where the access token has expired
3
if connected_account.status != "ACTIVE":
4
# Generate an authorization link for the user to complete OAuth flow
5
link_response = actions.get_authorization_link(
6
connection_name="GMAIL",
7
identifier="user_123"
8
)
9
10
# Display the authorization link to the user
11
print(f"🔗 Authorize Gmail access: {link_response.link}")
12
input("⎆ Press Enter after completing authorization...")
13
14
# In production, redirect users to the authorization URL instead of using input()
15
# The user will be redirected back to your app after successful authorization
```
4. ### Build a Google ADK agent
[Section titled “Build a Google ADK agent”](#build-a-google-adk-agent)
Build a simple agent that fetches the last 5 unread emails from the user’s inbox and generates a summary.
```python
1
from google.adk.agents import Agent
2
3
# Get Gmail tools from Scalekit in Google ADK format
4
gmail_tools = actions.google.get_tools(
5
providers=["GMAIL"],
6
identifier="user_123",
7
page_size=100
8
)
9
10
# Create a Google ADK agent with Gmail tools
11
gmail_agent = Agent(
12
name="gmail_assistant",
13
model="gemini-2.5-flash",
14
description="Gmail assistant that can read and manage emails",
15
instruction=(
16
"You are a helpful Gmail assistant. You can read, send, and organize emails. "
17
"When asked to fetch emails, focus on unread messages and provide clear summaries. "
18
"Always be helpful and professional."
19
),
20
tools=gmail_tools
21
)
22
23
# Use the agent to fetch and summarize unread emails
24
response = gmail_agent.process_request(
25
"fetch my last 5 unread emails and summarize them"
26
)
27
print(response)
```
---
# DOCUMENT BOUNDARY
---
# Google GenAI
> Learn how to use Google GenAI with Agent Auth.
# Google GenAI
[Section titled “Google GenAI”](#google-genai)
Google GenAI is a framework for building AI agents. It provides a set of tools and abstractions for building AI agents.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# LangChain
> Build a LangChain agent that authenticates users with Gmail and executes tools on their behalf using Scalekit Agent Auth.
This guide shows how to build a LangChain agent that authenticates users with Gmail via OAuth 2.0 and fetches their last 5 unread emails using Scalekit Agent Auth.
[Full code on GitHub ](https://github.com/scalekit-inc/sample-langchain-agent)
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Before you start, make sure you have:
* **Scalekit API credentials** — Client ID and Client Secret from [app.scalekit.com](https://app.scalekit.com)
* **OpenAI API Key** — Get from [OpenAI Platform](https://platform.openai.com/api-keys)
1. ### Set up your environment
[Section titled “Set up your environment”](#set-up-your-environment)
Install the Scalekit Python SDK and initialize the client. Get your credentials from **Developers → Settings → API Credentials** in the [dashboard](https://app.scalekit.com).
```sh
pip install scalekit-sdk-python langchain langchain-openai
```
```python
import scalekit.client
import os
from dotenv import load_dotenv
load_dotenv()
scalekit_client = scalekit.client.ScalekitClient(
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit_client.actions
```
2. ### Create a connected account
[Section titled “Create a connected account”](#create-a-connected-account)
Authorize a user to access their Gmail account by creating a connected account. This represents the user’s connection to their Gmail account:
```python
1
# Create a connected account for user if it doesn't exist already
2
response = actions.get_or_create_connected_account(
3
connection_name="gmail",
4
identifier="user_123" # unique identifier; replace with your system's user ID
5
)
6
connected_account = response.connected_account
7
8
print(f'Connected account created: {connected_account.id}')
```
3. ### Authenticate the user
[Section titled “Authenticate the user”](#authenticate-the-user)
For Scalekit to execute tools on behalf of the user, the user must grant authorization to access their Gmail account. Scalekit automatically handles the entire OAuth workflow, including token refresh.
```python
1
# If the user hasn't yet authorized the gmail connection or if the user's access token is expired,
2
# generate a magic link and redirect the user to this link so that the user can complete authorization
3
if(connected_account.status != "ACTIVE"):
4
link_response = actions.get_authorization_link(
5
connection_name="gmail",
6
identifier="user_123"
7
)
8
print(f"🔗click on the link to authorize gmail", link_response.link)
9
input(f"⎆ Press Enter after authorizing gmail...")
10
11
# In a real app, redirect the user to this URL so that the user can complete the authentication process for their gmail account
```
4. ### Build a LangChain agent
[Section titled “Build a LangChain agent”](#build-a-langchain-agent)
Build a simple agent that fetches the last 5 unread emails from the user’s inbox and generates a summary.
```python
1
from langchain_openai import ChatOpenAI
2
from langchain.agents import AgentExecutor, create_openai_tools_agent
3
from langchain_core.prompts import SystemMessagePromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate,PromptTemplate, ChatPromptTemplate
4
5
# use scalekit SDK to fetch all GMAIL tools in langchain format
6
tools = actions.langchain.get_tools(
7
identifier="user_123",
8
providers = ["GMAIL"], # all tools for provider used by default
9
page_size=100
10
)
11
12
prompt = ChatPromptTemplate.from_messages([
13
SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template="You are a helpful assistant. Use tools if needed")),
14
MessagesPlaceholder(variable_name="chat_history", optional=True),
15
HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template="{input}")),
16
MessagesPlaceholder(variable_name="agent_scratchpad")
17
])
18
19
llm = ChatOpenAI(model="gpt-4o")
20
agent = create_openai_tools_agent(llm, tools, prompt)
21
agent_executor = AgentExecutor(agent=agent, tools=tools,verbose=True)
22
result = agent_executor.invoke({"input":"fetch my last 5 unread emails and summarize it"}
23
)
```
---
# DOCUMENT BOUNDARY
---
# Mastra
> Learn how to use Mastra with Agent Auth.
# Mastra
[Section titled “Mastra”](#mastra)
Mastra is a framework for building AI agents. It provides a set of tools and abstractions for building AI agents.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# MCP
> Learn how to use MCP with Agent Auth.
# MCP
[Section titled “MCP”](#mcp)
MCP is a framework for building AI agents. It provides a set of tools and abstractions for building AI agents.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# OpenAI
> Learn how to use OpenAI with Agent Auth.
# OpenAI
[Section titled “OpenAI”](#openai)
OpenAI is a framework for building AI agents. It provides a set of tools and abstractions for building AI agents.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# Vercel
> Learn how to use Vercel with Agent Auth.
# Vercel
[Section titled “Vercel”](#vercel)
Vercel is a framework for building AI agents. It provides a set of tools and abstractions for building AI agents.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# Give your agent tool access via MCP
> Create a per-user MCP server with whitelisted, pre-authenticated tools — then hand your agent a single URL.
When your agent needs to act on behalf of a user — reading their email, creating calendar events — each user must authenticate to each service separately. Managing those credentials in your agent adds complexity and security risk.
Scalekit solves this with per-user MCP servers. You define which tools and connections a server exposes, and Scalekit gives you a unique, pre-authenticated URL for each user. Hand that URL to your agent — it calls tools through MCP, Scalekit handles the auth. MCP servers only support Streamable HTTP transport.
Testing only — not for production
This feature is in beta and intended for testing purposes only. Do not use it in production environments.
## How it works
[Section titled “How it works”](#how-it-works)
Two objects are central to this model:
| Object | What it is | Created |
| ---------------- | ------------------------------------------------------------------------ | ----------------- |
| **MCP config** | A reusable template that defines which connections and tools are exposed | Once, by your app |
| **MCP instance** | A per-user instantiation of a config, with its own URL | Once per user |
Your app creates a config once, then calls `ensure_instance` whenever a new user needs access. Scalekit generates a unique URL for that user. When the agent calls tools through that URL, Scalekit routes each call using the user’s pre-authorized credentials.

## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Before you start, make sure you have:
* **Scalekit API credentials** — Go to **Dashboard → Settings** and copy your `environment_url`, `client_id` and `client_secret`
* **Gmail and Google Calendar connections configured in Scalekit:**
* **Gmail**: **Dashboard → Connections** (Agent Auth) → Create Connection → Gmail → set `Connection Name = MY_GMAIL` → Save
* **Google Calendar**: **Dashboard → Connections** (Agent Auth) → Create Connection → Google Calendar → set `Connection Name = MY_CALENDAR` → Save
1. ## Install the SDK and initialize the client
[Section titled “Install the SDK and initialize the client”](#install-the-sdk-and-initialize-the-client)
Install the Scalekit Python SDK:
```sh
pip install scalekit-sdk-python python-dotenv>=1.0.1
```
Initialize the client using your environment credentials:
```python
import os
import scalekit.client
from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping
scalekit_client = scalekit.client.ScalekitClient(
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
env_url=os.getenv("SCALEKIT_ENV_URL"),
)
my_mcp = scalekit_client.actions.mcp
```
2. ## Create an MCP config
[Section titled “Create an MCP config”](#create-an-mcp-config)
An MCP config is a reusable template. It declares which connections and tools your server exposes. Create it once — not once per user.
```python
cfg_response = my_mcp.create_config(
name="reminder-manager",
description="Summarizes latest email and creates a reminder event",
connection_tool_mappings=[
McpConfigConnectionToolMapping(
connection_name="MY_GMAIL",
tools=["gmail_fetch_mails"],
),
McpConfigConnectionToolMapping(
connection_name="MY_CALENDAR",
tools=["googlecalendar_create_event"],
),
],
)
config_name = cfg_response.config.name
```
3. ## Get a per-user MCP URL
[Section titled “Get a per-user MCP URL”](#get-a-per-user-mcp-url)
Call `ensure_instance` to get a unique MCP URL for a specific user. If an instance already exists for that user, Scalekit returns it — so it’s safe to call on every login.
```python
inst_response = my_mcp.ensure_instance(
config_name=config_name,
user_identifier="john-doe",
)
mcp_url = inst_response.instance.url
print("MCP URL:", mcp_url)
```
Before the agent can use this URL, the user must authorize each connection. Retrieve the auth links and surface them to the user:
```python
auth_state_response = my_mcp.get_instance_auth_state(
instance_id=inst_response.instance.id,
include_auth_links=True,
)
for conn in getattr(auth_state_response, "connections", []):
print("Connection:", conn.connection_name,
"| Status:", conn.connected_account_status,
"| Auth link:", conn.authentication_link)
```
Complete authentication
Open the printed links in your browser and complete authentication for each connection.
In production, surface these links to users via your app UI, email, or a Slack message. Poll `get_instance_auth_state` (without `include_auth_links`) to check when a user has completed authorization before passing the URL to your agent.
At this point you have a per-user MCP URL. You can pass it to any spec-compliant MCP client — MCP Inspector, Claude Desktop, or an agent framework. The next step shows an example using LangChain.
4. ## Connect an agent (LangChain example)
[Section titled “Connect an agent (LangChain example)”](#connect-an-agent-langchain-example)
Install the LangChain dependencies:
```sh
pip install langgraph>=0.6.5 langchain-mcp-adapters>=0.1.9 openai>=1.53.0
```
Set your OpenAI API key:
```sh
export OPENAI_API_KEY=your-openai-api-key
```
Pass the MCP URL to a LangChain agent. The agent discovers available tools automatically — no additional auth configuration required:
```python
import asyncio
from langgraph.prebuilt import create_react_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
async def run_agent(mcp_url: str):
client = MultiServerMCPClient(
{
"reminder_demo": {
"transport": "streamable_http",
"url": mcp_url,
},
}
)
tools = await client.get_tools()
agent = create_react_agent("openai:gpt-4.1", tools)
response = await agent.ainvoke({
"messages": "Get my latest email and create a calendar reminder in the next 15 minutes."
})
print(response)
asyncio.run(run_agent(mcp_url))
```
MCP client compatibility
This MCP server works with MCP Inspector, Claude Desktop, and any spec-compliant MCP client. ChatGPT’s beta connector may not work correctly — it is still in beta and does not fully implement the MCP specification.
Full working code for all steps above is on [GitHub](https://github.com/scalekit-inc/python-connect-demos/tree/main/mcp).
## Next steps
[Section titled “Next steps”](#next-steps)
[LangChain integration ](/agent-auth/frameworks/langchain)Use LangChain to build agents that connect to Scalekit MCP servers.
[Google ADK integration ](/agent-auth/frameworks/google-adk)Connect Scalekit tools to agents built with Google's Agent Development Kit.
[Manage connections ](/agent-auth/connections)Learn how to configure and manage provider connections in Scalekit.
---
# DOCUMENT BOUNDARY
---
# OpenClaw skill
> Connect OpenClaw agents to third-party services through Scalekit. Supports LinkedIn, Notion, Slack, Gmail, and 50+ providers.
Use the Scalekit Agent Auth skill for [OpenClaw](https://github.com/scalekit-inc/openclaw-skill) to let your AI agents execute actions on third-party services directly from conversations. Search LinkedIn, read Notion pages, send Slack messages, query Snowflake, and more — all through Scalekit Connect without storing tokens or API keys in your agent.
Security considerations for AI agents
Scalekit stores tokens and API keys securely with full audit logging. OpenClaw, like all AI agent frameworks, is vulnerable to prompt injection and other agent-level attacks. Follow security best practices to protect your instance.
When you ask Claude to interact with a third-party service, the skill:
* Finds the configured provider in Scalekit (e.g., [Gmail connection setup](/reference/agent-connectors/gmail/)) and identifies which connection to use based on the requested action
* Checks if the connection is active. For OAuth connections, it generates a magic link for new authorizations. For API key connections, it provides Dashboard guidance for setup
* Retrieves available tools and their parameter schemas for the provider, determining what actions are possible
* Calls the right tool with the correct parameters and returns the result to your conversation
* If no tool exists for the action, routes the request through Scalekit’s HTTP proxy, making direct API calls on your behalf
Automatic auth flow detection
The skill automatically detects whether a connection uses OAuth or an API key and applies the correct auth flow — no configuration needed.
Your agent never stores tokens or API keys. Scalekit acts as a token vault, managing all OAuth tokens, API keys, and credentials. The skill retrieves only what it needs at runtime, scoped to the requesting user.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* [OpenClaw](https://openclaw.ai) installed and configured
* A Scalekit account with Agent Auth enabled — [sign up at app.scalekit.com](https://app.scalekit.com)
* `python3` and `uv` available in your PATH
## Get started
[Section titled “Get started”](#get-started)
1. ## Install the skill
[Section titled “Install the skill”](#install-the-skill)
Install the skill from ClawHub:
```bash
clawhub install scalekit-agent-auth
```
2. ## Configure credentials
[Section titled “Configure credentials”](#configure-credentials)
Add your Scalekit credentials to `.env` in your project root:
.env
```bash
1
TOOL_CLIENT_ID=skc_your_client_id # Your Scalekit client ID
2
TOOL_CLIENT_SECRET=your_client_secret # Your Scalekit client secret
3
TOOL_ENV_URL=https://your-env.scalekit.cloud # Your Scalekit environment URL
4
TOOL_IDENTIFIER=your_default_user_identifier # Default user context for tool calls
```
| Parameter | Description |
| -------------------- | --------------------------------------------------- |
| `TOOL_CLIENT_ID` | Your Scalekit client ID Required |
| `TOOL_CLIENT_SECRET` | Your Scalekit client secret Required |
| `TOOL_ENV_URL` | Your Scalekit environment URL Required |
| `TOOL_IDENTIFIER` | Default user context for all tool calls Recommended |
Environment variable security
Never commit `.env` files to version control. Add `.env` to your `.gitignore` file to prevent accidental exposure of credentials.
3. ## Usage
[Section titled “Usage”](#usage)
* Gmail
```txt
You: Show me my latest unread emails
```
OpenClaw will automatically:
1. Look up the `GMAIL` connection
2. Verify it’s active (or generate a magic link to authorize if needed)
3. Fetch the `gmail_list_emails` tool schema
4. Return your latest unread emails
* Notion
```txt
You: Read my Notion page https://notion.so/My-Page-abc123
```
OpenClaw will:
1. Look up the `NOTION` connection
2. If not yet authorized, generate a magic link for you to complete OAuth
3. Fetch the `notion_page_get` tool schema
4. Return the page content
## Supported providers
[Section titled “Supported providers”](#supported-providers)
Any provider configured in Scalekit works with the OpenClaw skill — including Notion, Slack, Gmail, Google Sheets, GitHub, Salesforce, HubSpot, Linear, Snowflake, Exa, HarvestAPI, and 50+ more.
[Browse connections ](/guides/integrations/agent-connectors)See all supported providers in the Scalekit dashboard
[ClawHub listing ](https://clawhub.dev/skills/scalekit-agent-auth)Install scalekit-agent-auth from ClawHub
## Common scenarios
[Section titled “Common scenarios”](#common-scenarios)
How do I authorize a new connection?
When you request an action for a connection that isn’t yet authorized, the skill automatically generates a magic link. Click the link to complete OAuth authorization in your browser. After authorization, return to your OpenClaw conversation and retry the action.
For API key-based connections (like Snowflake), you’ll need to configure credentials directly in the Scalekit Dashboard under **Connections**.
How do I switch between different user contexts?
Set `TOOL_IDENTIFIER` in your `.env` file to define a default user context. All tool calls will execute with that user’s permissions and connected accounts.
To use a different user context for a specific conversation, you can override the identifier by setting it in your OpenClaw configuration or passing it as a parameter when invoking the skill.
Why am I seeing a “connection not found” error?
This error occurs when the skill cannot find a configured connection for the requested provider. Check the following:
1. **Verify the connection exists** — Go to **Dashboard > Connections** and confirm the provider is configured
2. **Check connection status** — Ensure the connection shows as “Active” in the dashboard
3. **Verify environment** — Confirm you’re using the correct `TOOL_ENV_URL` for your environment
How do I debug tool execution issues?
Enable debug logging in your OpenClaw configuration to see detailed information about tool calls:
```bash
TOOL_DEBUG=true
```
This logs the tool name, parameters, and response for each execution, helping you identify issues with parameter formatting or API responses.
---
# DOCUMENT BOUNDARY
---
# Overview
> Learn about Agent Auth, Scalekit's authentication solution for securely connecting to third-party applications through OAuth 2.0
Agent Auth is Scalekit’s authentication solution that enables your applications to securely connect to third-party services on behalf of your users. It handles the complexity of OAuth flows, token management, and multi-provider authentication for popular business applications like Gmail, Slack, Jira, and more.
## What is Agent Auth?
[Section titled “What is Agent Auth?”](#what-is-agent-auth)
Agent Auth simplifies third-party authentication by providing:
* **Multi-provider OAuth**: Support for OAuth 2.0 flows across all major providers
* **Unified authentication API**: Single interface for managing connections to any provider
* **Automatic token management**: Token refresh, storage, and lifecycle handling
* **Flexible authentication modes**: Use Scalekit-managed OAuth or bring your own credentials
* **Secure token handling**: Encrypted token storage and transmission
## Key concepts
[Section titled “Key concepts”](#key-concepts)
### Providers
[Section titled “Providers”](#providers)
Providers are third-party applications that your users can authenticate with. Agent Auth supports OAuth 2.0 flows for popular platforms including Gmail, Slack, GitHub, Jira, and many more.
### Connections
[Section titled “Connections”](#connections)
Connections define the authentication configuration for a specific provider. Each connection contains:
* **Authentication credentials** (OAuth client ID/secret, API keys)
* **OAuth configuration** (authorization URLs, token endpoints, scopes)
* **Provider-specific settings** (rate limits, API versions)
### Connected accounts
[Section titled “Connected accounts”](#connected-accounts)
Connected accounts represent the authenticated link between a user in your application and their account on a third-party provider. Each connected account maintains:
* **Authentication state** (active, expired, revoked)
* **Access tokens** and refresh tokens with automatic refresh handling
* **Granted OAuth scopes** and permissions
* **Token expiration** tracking and lifecycle management
## Architecture overview
[Section titled “Architecture overview”](#architecture-overview)
## How authentication works
[Section titled “How authentication works”](#how-authentication-works)
### 1. Configure provider connections
[Section titled “1. Configure provider connections”](#1-configure-provider-connections)
Set up OAuth credentials for each provider you want to support:
```javascript
1
// Create a connection for Gmail with OAuth 2.0
2
const gmailConnection = await agentConnect.connections.create({
3
provider: 'gmail',
4
auth_type: 'oauth2',
5
credentials: {
6
client_id: 'your-gmail-client-id',
7
client_secret: 'your-gmail-client-secret'
8
},
9
scopes: ['https://www.googleapis.com/auth/gmail.send']
10
});
```
### 2. Initiate OAuth flow for users
[Section titled “2. Initiate OAuth flow for users”](#2-initiate-oauth-flow-for-users)
When users want to connect their accounts, create a connected account and redirect them to complete OAuth:
```javascript
1
// Create a connected account for a user
2
const connectedAccount = await agentConnect.accounts.create({
3
connection_id: gmailConnection.id,
4
identifier: 'user_123',
5
identifier_type: 'user_id'
6
});
7
8
// Generate OAuth authorization URL
9
const authUrl = await agentConnect.accounts.getAuthUrl(connectedAccount.id);
10
// Redirect user to authUrl to authenticate with the provider
```
### 3. Handle OAuth callback
[Section titled “3. Handle OAuth callback”](#3-handle-oauth-callback)
After the user authorizes your application, the provider redirects back with an authorization code. Agent Auth automatically exchanges this code for access and refresh tokens:
```javascript
1
// Agent Auth handles the OAuth callback and token exchange
2
// Tokens are securely stored and automatically refreshed when needed
3
const account = await agentConnect.accounts.get(connectedAccount.id);
4
// account.status will be 'active' once authentication is complete
```
## Supported providers
[Section titled “Supported providers”](#supported-providers)
Agent Auth supports OAuth authentication for a wide range of popular business applications:
Communication
* Gmail (Google Workspace)
* Outlook (Microsoft 365)
* Slack
* Microsoft Teams
* Discord
Productivity
* Google Calendar
* Microsoft Calendar
* Google Drive
* OneDrive
* Notion
Project Management
* Jira
* Asana
* Trello
* Monday.com
* Linear
Development
* GitHub
* GitLab
* Bitbucket
* Figma
* Vercel
## Common authentication scenarios
[Section titled “Common authentication scenarios”](#common-authentication-scenarios)
### Multi-provider authentication
[Section titled “Multi-provider authentication”](#multi-provider-authentication)
Enable users to connect multiple third-party accounts in your application:
```javascript
1
// Allow users to authenticate with both Gmail and Slack
2
const gmailAccount = await agentConnect.accounts.create({
3
connection_id: gmailConnection.id,
4
identifier: 'user_123',
5
identifier_type: 'user_id'
6
});
7
8
const slackAccount = await agentConnect.accounts.create({
9
connection_id: slackConnection.id,
10
identifier: 'user_123',
11
identifier_type: 'user_id'
12
});
13
14
// Generate OAuth URLs for each provider
15
const gmailAuthUrl = await agentConnect.accounts.getAuthUrl(gmailAccount.id);
16
const slackAuthUrl = await agentConnect.accounts.getAuthUrl(slackAccount.id);
```
### Organization-level connections
[Section titled “Organization-level connections”](#organization-level-connections)
Authenticate once for an entire organization:
```javascript
1
// Create organization-level connection for shared access
2
const orgConnection = await agentConnect.accounts.create({
3
connection_id: jiraConnection.id,
4
identifier: 'org_456',
5
identifier_type: 'org_id'
6
});
7
8
// All users in the organization can access this connection
9
const authUrl = await agentConnect.accounts.getAuthUrl(orgConnection.id);
```
### Token lifecycle management
[Section titled “Token lifecycle management”](#token-lifecycle-management)
Agent Auth automatically handles token refresh and expiration:
```javascript
1
// Retrieve connected account - tokens are automatically refreshed if expired
2
const account = await agentConnect.accounts.get(connectedAccount.id);
3
4
// Check authentication status
5
if (account.status === 'active') {
6
// User is authenticated and tokens are valid
7
} else if (account.status === 'expired') {
8
// Re-authentication required
9
const reAuthUrl = await agentConnect.accounts.getAuthUrl(account.id);
10
}
```
## Key benefits
[Section titled “Key benefits”](#key-benefits)
### Developer experience
[Section titled “Developer experience”](#developer-experience)
* **Unified authentication API**: Single interface for OAuth across all providers
* **Automatic token refresh**: No manual token lifecycle management required
* **Pre-built OAuth flows**: Skip complex OAuth implementation for each provider
* **Multi-language SDKs**: Support for popular programming languages
* **Standardized error handling**: Consistent error responses across providers
### Security and compliance
[Section titled “Security and compliance”](#security-and-compliance)
* **OAuth 2.0 standard**: Industry-standard authentication protocol
* **Encrypted token storage**: Secure storage and transmission of access tokens
* **Automatic token rotation**: Refresh tokens automatically to minimize exposure
* **Audit logging**: Complete audit trail of authentication events
* **SOC 2 certified**: Enterprise-grade security standards
## Authentication options
[Section titled “Authentication options”](#authentication-options)
Agent Auth supports multiple authentication approaches:
### Scalekit-managed OAuth
[Section titled “Scalekit-managed OAuth”](#scalekit-managed-oauth)
Use Scalekit’s shared OAuth applications for quick setup:
* **Fast setup**: Get started in minutes without registering OAuth apps
* **Shared credentials**: Pre-configured OAuth credentials for all providers
* **Zero configuration**: No need to manage client IDs or secrets
* **Perfect for**: Development, testing, proof of concepts
### Bring Your Own OAuth (BYOO)
[Section titled “Bring Your Own OAuth (BYOO)”](#bring-your-own-oauth-byoo)
Use your own OAuth applications for complete control:
* **Custom branding**: Users see your application name and logo during OAuth
* **Higher rate limits**: Dedicated quotas for your OAuth application
* **Direct relationships**: Establish direct OAuth connections with providers
* **Enhanced control**: Full control over OAuth scopes and permissions
* **Perfect for**: Production applications, enterprise customers
## Getting started
[Section titled “Getting started”](#getting-started)
Ready to start using Agent Auth? Here’s what you need to do:
[Quickstart Guide ](/agent-auth/quickstart)
[Authentication Flows ](/agent-auth/authentication/auth-flows-comparison)
[Providers ](/agent-auth/providers)
## Support and resources
[Section titled “Support and resources”](#support-and-resources)
### Documentation
[Section titled “Documentation”](#documentation)
* **Authentication guides**: Step-by-step OAuth integration guides
* **API reference**: Complete authentication API documentation
* **SDK documentation**: Language-specific authentication examples
* **Best practices**: Security and token management guidelines
### Community and support
[Section titled “Community and support”](#community-and-support)
* **Developer community**: Join other developers using Agent Auth
* **Support portal**: Get help with authentication issues
* **Professional services**: Expert assistance for complex OAuth integrations
Note
**Ready to get started?** Check out our [Quickstart Guide](/agent-auth/quickstart) to implement your first OAuth integration with Agent Auth in minutes.
Agent Auth simplifies third-party authentication so you can focus on building features instead of managing OAuth flows. Start building today and provide seamless authentication experiences for your users.
---
# DOCUMENT BOUNDARY
---
# Providers
> Learn about third-party application providers supported by Agent Auth and how they enable tool execution across different platforms.
Providers in Agent Auth represent third-party applications that your users can connect to and interact with through Scalekit’s unified API. Each provider offers a set of tools and capabilities that can be executed on behalf of connected users.
## What are providers?
[Section titled “What are providers?”](#what-are-providers)
Providers are pre-configured integrations with popular third-party applications that enable your users to:
* **Connect their accounts** using secure authentication methods
* **Execute tools and actions** through a unified API interface
* **Access data and functionality** from external applications
* **Maintain secure connections** with proper authorization scopes
## Supported providers
[Section titled “Supported providers”](#supported-providers)
[Browse all agent connectors ](/guides/integrations/agent-connectors)
Next, learn how to configure [Connections](/agent-auth/connections) for your chosen providers to enable user authentication and tool execution.
---
# DOCUMENT BOUNDARY
---
# Invoke tools for your AI agent
> Execute tools directly, customize their behavior with modifiers, and build agentic workflows where LLMs drive tool selection.
Agent Auth supports three approaches to tool calling: execute tools directly with explicit parameters, customize tool behavior with pre- and post-modifiers, or let an LLM select and invoke tools automatically based on user input.
## Direct tool execution
[Section titled “Direct tool execution”](#direct-tool-execution)
Using Scalekit SDK, you can execute any action on behalf of a user using the following parameters:
* user context
* `tool_name`
* `tool_input_parameters`
- Python
```python
1
# Fetch recent emails
2
tool_response = actions.execute_tool(
3
# tool input parameters
4
tool_input={
5
'query': 'is:unread',
6
'max_results': 5
7
},
8
# tool name to execute
9
tool_name='gmail_fetch_mails',
10
# connected_account gives the user context
11
connected_account_id=connected_account.id,
12
)
13
14
print(f'Recent emails: {tool_response.result}')
```
- Node.js
```typescript
1
// Fetch recent emails
2
const toolResponse = await actions.executeTool({
3
// tool name to execute
4
toolName: 'gmail_fetch_mails',
5
// connectedAccountId from a prior getOrCreateConnectedAccount call
6
connectedAccountId: 'your_connected_account_id',
7
// tool input parameters
8
toolInput: {
9
query: 'is:unread',
10
max_results: 5,
11
},
12
});
13
14
console.log('Recent emails:', toolResponse.result);
```
## Customize with modifiers
[Section titled “Customize with modifiers”](#customize-with-modifiers)
Tool modifiers intercept and modify tool inputs and outputs using decorators.
* **Pre-modifiers**: Modify tool inputs before execution
* **Post-modifiers**: Modify tool outputs after execution
Common uses
* Reduce response size to prevent LLM context overloading
* Filter emails to unread only
* Add consistent parameters
* Transform data formats
### Pre-modifiers
[Section titled “Pre-modifiers”](#pre-modifiers)
Pre-modifiers modify tool inputs before execution to enforce consistent filters, add security constraints, override LLM decisions with required behavior, or set default configurations.
Example: Gmail unread filter
```python
1
from scalekit.actions.models.tool_input_output import ToolInput
2
3
# For example, we can modify the query to only fetch unread emails
4
# regardless of what the user asks for or what the LLM determines.
5
@actions.pre_modifier(tool_names=["gmail_fetch_mails"])
6
def gmail_pre_modifier(tool_input: ToolInput):
7
tool_input['query'] = 'is:unread'
8
return tool_input
```
This modifier:
* Intercepts all calls to `gmail_fetch_mails`
* Forces the query to always search for unread emails only
* Ensures consistent behavior regardless of user input or LLM interpretation
**Multiple tools example**
```python
1
@actions.pre_modifier(tool_names=["gmail_fetch_mails", "gmail_search_emails"])
2
def email_security_modifier(tool_input: ToolInput):
3
# Add security constraints to all email operations
4
tool_input['include_spam'] = False
5
tool_input['max_results'] = min(tool_input.get('max_results', 10), 50)
6
return tool_input
```
### Post-modifiers
[Section titled “Post-modifiers”](#post-modifiers)
Post-modifiers modify tool outputs after execution to reduce token usage, transform formats for LLM consumption, extract specific data, or standardize output structure.
Response format
Post-modifiers must always return a dictionary with a `"response"` key: `{"response": your_data}`
Example: Gmail response filtering
```python
1
from scalekit.actions.models.tool_input_output import ToolOutput
2
3
# Sometimes, the tool output needs to be modified in a deterministic way after the tool is executed.
4
# For example, we can modify the output to only return the first email snippet regardless of what the tool returns.
5
# This is an effective way to reduce the amount of data that is returned to the LLM to save on tokens.
6
@actions.post_modifier(tool_names=["gmail_fetch_mails"])
7
def gmail_post_modifier(output: ToolOutput):
8
# Only return the first email snippet
9
# Should return a dict
10
# Response should be a dict with a key 'response'
11
for snippet in output['messages']:
12
print(f"Email snippet: {snippet['snippet']}")
13
return {"response": output['messages'][0]['snippet']}
```
This modifier:
* Processes the response from `gmail_fetch_mails`
* Extracts only the first email snippet instead of returning all emails
* Reduces token usage by sending minimal data to the LLM
## Agentic tool calling
[Section titled “Agentic tool calling”](#agentic-tool-calling)
Let an LLM determine which tool to call and with what parameters based on user input. This quickstart uses LangChain to build an agent that authenticates a user with Gmail and fetches their last 5 unread emails.
**Prerequisites**: Scalekit API credentials (Client ID and Client Secret) and a Python development environment.
1. ### Set up your environment
[Section titled “Set up your environment”](#set-up-your-environment)
Install the Scalekit Python SDK and initialize the client with your API credentials:
```sh
pip install scalekit-sdk-python langchain
```
```python
import scalekit.client
import os
scalekit_client = scalekit.client.ScalekitClient(
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit_client.actions
```
2. ### Create a connected account
[Section titled “Create a connected account”](#create-a-connected-account)
Authorize a user to access their Gmail account by creating a connected account. This represents the user’s connection to their Gmail account:
```python
1
# Create a connected account for user if it doesn't exist already
2
response = actions.get_or_create_connected_account(
3
connection_name="gmail",
4
identifier="user_123"
5
)
6
connected_account = response.connected_account
7
8
print(f'Connected account created: {connected_account.id}')
```
3. ### Authenticate the user
[Section titled “Authenticate the user”](#authenticate-the-user)
For Scalekit to execute tools on behalf of the user, the user must grant authorization to access their Gmail account. Scalekit automatically handles the entire OAuth workflow, including token refresh.
```python
1
# If the user hasn't yet authorized the gmail connection or if the user's access token is expired,
2
# generate a magic link and redirect the user to this link so that the user can complete authorization
3
if(connected_account.status != "ACTIVE"):
4
link_response = actions.get_authorization_link(
5
connection_name="gmail",
6
identifier="user_123"
7
)
8
print(f"🔗click on the link to authorize gmail", link_response.link)
9
input(f"⎆ Press Enter after authorizing gmail...")
10
11
# In a real app, redirect the user to this URL so that the user can complete the authentication process for their gmail account
```
4. ### Build a LangChain agent
[Section titled “Build a LangChain agent”](#build-a-langchain-agent)
Build a simple agent that fetches the last 5 unread emails from the user’s inbox and generates a summary.
```python
1
from langchain_core.prompts import SystemMessagePromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate,PromptTemplate, ChatPromptTemplate
2
3
# use scalekit SDK to fetch all GMAIL tools in langchain format
4
tools = actions.langchain.get_tools(
5
identifier=identifier,
6
providers = ["GMAIL"], # all tools for provider used by default
7
page_size=100
8
)
9
10
prompt = ChatPromptTemplate.from_messages([
11
SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template="You are a helpful assistant. Use tools if needed")),
12
MessagesPlaceholder(variable_name="chat_history", optional=True),
13
HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template="{input}")),
14
MessagesPlaceholder(variable_name="agent_scratchpad")
15
])
16
17
llm = ChatOpenAI(model="gpt-4o")
18
agent = create_openai_tools_agent(llm, tools, prompt)
19
agent_executor = AgentExecutor(agent=agent, tools=tools,verbose=True)
20
result = agent_executor.invoke({"input":"fetch my last 5 unread emails and summarize it"}
21
)
```
## Next steps
[Section titled “Next steps”](#next-steps)
For more detailed framework-specific implementations, explore the AI framework guides:
[LangChain Framework ](/agent-auth/frameworks/langchain)
[Google ADK Framework ](/agent-auth/frameworks/google-adk)
[OpenClaw ](/agent-auth/openclaw)
---
# DOCUMENT BOUNDARY
---
# Authorize a user
> Learn how to authorize users for successful tool execution with Agent Auth.
Scalekit provides a completely managed authentication platform to handle complex OAuth2.0, API Keys, Bearer Tokens and any other API authentication protocols required by third party applications to execute tool calls on behalf of users.
This managed authentication handling enables you to build powerful agents without having to worry about handling user authentication and API authentication across different applications like Salesforce, Hubspot, GMail, Google Calendar etc.
## Authorize a user
[Section titled “Authorize a user”](#authorize-a-user)
If you are building an agent that needs to execute actions on behalf of a user, your agent needs to get authorization from user to give access to their application.
The following code sample helps your agent complete user authorization required to make a successful authenticated tool call.
* Python
```python
1
link_response = actions.get_authorization_link(
2
connection_name="gmail", # connection name to which the user needs to grant access
3
identifier="user_123" # unique user id
4
)
5
print(f"click on the link to authorize gmail", link_response.link)
6
input(f"Press Enter after authorizing gmail...")
```
* Node.js
```typescript
1
const linkResponse = await actions.getAuthorizationLink({
2
connectionName: 'gmail', // connection name to which the user needs to grant access
3
identifier: 'user_123', // unique user id
4
});
5
console.log('click on the link to authorize gmail', linkResponse.link);
6
// In production, redirect the user to linkResponse.link to complete the OAuth flow
```
## Check Authorization Status
[Section titled “Check Authorization Status”](#check-authorization-status)
If you would like to check whether the user has completed authorization for a given application,
* Python
```python
1
response = actions.get_or_create_connected_account(
2
connection_name="gmail",
3
identifier="user_123"
4
)
5
connected_account = response.connected_account
6
print(f"Authorization status of the connected account", connected_account.status)
```
* Node.js
```typescript
1
const response = await actions.getOrCreateConnectedAccount({
2
connectionName: 'gmail',
3
identifier: 'user_123',
4
});
5
const connectedAccount = response.connectedAccount;
6
console.log('Authorization status of the connected account', connectedAccount?.status);
```
---
# DOCUMENT BOUNDARY
---
# Pre and Post Processors
> Learn how to create pre and post processor workflows that are run before or after tool execution with Agent Auth.
Custom pre and post processors are a way to create custom workflows that are run before or after tool execution with Agent Auth. They are useful for:
* Validating and transforming input data
* Processing and Formatting output data
* Adding additional context to the tool execution
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# Custom Tools
> Learn how to create custom tools with Agent Auth.
# Custom Tools
[Section titled “Custom Tools”](#custom-tools)
Custom tools are a way to create custom tools that can be used in Agent Auth.
## Usage
[Section titled “Usage”](#usage)
---
# DOCUMENT BOUNDARY
---
# Tools Overview
> Learn about tools in Agent Auth - the standardized functions that enable you to perform actions across different third-party providers.
LLMs today are very powerful reasoning and answering machines but their ability is restricted to data sets that they are trained upon and cannot natively interact with web services or saas applications. Tool Calling or Function Calling is how you extend the capabilities of these models to interact and take actions in third party applications on behalf of the users.
For example, if you would like to build an email summarizer agent, there are a few challenges that you need to tackle:
1. How to give agents access to gmail
2. How to authorize these agents access to my gmail account
3. What should be the appropriate input parameters to access gmail based on user context and query
Agent Auth product solves these problems by giving you simple abstractions using our SDK to help you give additional capabilities to the agents you are building regardless of the underlying model and agent framework in three simple steps.
1. Use Scalekit SDK to fetch all the appropriate tools
2. Complete user authorization handling in one single line of code
3. Use Scalekit’s optimized tool metadata and pass it to the underlying model for optimal tool selection and input parameters.
## Tool Metadata
[Section titled “Tool Metadata”](#tool-metadata)
Every tool in Agent Auth follows a consistent structure with a name, description and structured input and output schema. Agentic frameworks like Langchain can work with the underlying LLMs to select the right tool to solve the user’s query based on the tool metadata.
### Sample Tool definition
[Section titled “Sample Tool definition”](#sample-tool-definition)
```json
1
{
2
"name": "gmail_send_email",
3
"display_name": "Send Email",
4
"description": "Send an email message to one or more recipients",
5
"provider": "gmail",
6
"category": "communication",
7
"input_schema": {
8
"type": "object",
9
"properties": {
10
"to": {
11
"type": "array",
12
"items": {"type": "string", "format": "email"},
13
"description": "Email addresses of recipients"
14
},
15
"subject": {
16
"type": "string",
17
"description": "Email subject line"
18
},
19
"body": {
20
"type": "string",
21
"description": "Email body content"
22
}
23
},
24
"required": ["to", "subject", "body"]
25
},
26
"output_schema": {
27
"type": "object",
28
"properties": {
29
"message_id": {
30
"type": "string",
31
"description": "Unique identifier for the sent message"
32
},
33
"status": {
34
"type": "string",
35
"enum": ["sent", "queued", "failed"],
36
"description": "Status of the email sending operation"
37
}
38
}
39
}
40
}
```
## Best practices
[Section titled “Best practices”](#best-practices)
1. **Tool Selection:** Even though tools provide additional capabilities to the agents, the real challenge in leveraging underlying LLMs capability to select the right tool to solve the job at hand. And LLMs do a poor job when you throw all the available tools you have at your disposal and ask LLMs to pick the right tool. So, be sure to limit the number of tools that you provide in the context to the LLM so that they do a good job in tool selection and filling in the appropriate input parameters to actually execute a certain action successfully.
2. **Add deterministic overrides in undeterministic workflows:** Because LLMs are unpredictable super machines, do not trust them to reliably execute the same workflow every single time in the exact same manner. If your agent has some deterministic patterns or workflows, use the pre-execution modifiers to always set exact input parameters for a given tool. For example, if your agent always reads only unread emails, create a pre-execution modifier to add `is:unread` to the query input param while fetching emails using gmail\_fetch\_emails tool.
3. **Context Window Awareness:** Similar to the point above, always be conscious of overloading context window of the underlying models. Don’t send the entire tool execution response/output to the underlying model for processing the execution response. Use the post-execution modifiers to select only the required and necessary fields in the tool output response before sending the data to the LLMs.
***
Tools are the fundamental building blocks through which you can give real world capabilities for the agents you are building. By understanding how to use them effectively, you can build sophisticated agents that seamlessly connect your application to the tools your users already love.
---
# DOCUMENT BOUNDARY
---
# Proxy Tools
> Learn how to make direct API calls to providers using Agent Auth's proxy tools.
Custom tool definitions allow you to create specialized tools tailored to your specific business needs. You can combine multiple provider tools, add custom logic, and create reusable workflows that go beyond standard tool functionality.
## What are custom tools?
[Section titled “What are custom tools?”](#what-are-custom-tools)
Custom tools are user-defined functions that:
* **Extend existing tools**: Build on top of standard provider tools
* **Combine multiple operations**: Create workflows that use multiple tools
* **Add business logic**: Include custom validation, processing, and formatting
* **Create reusable patterns**: Standardize common operations across your team
* **Integrate with external systems**: Connect to your own APIs and services
## Custom tool structure
[Section titled “Custom tool structure”](#custom-tool-structure)
Every custom tool follows a standardized structure:
```javascript
1
{
2
name: 'custom_tool_name',
3
display_name: 'Custom Tool Display Name',
4
description: 'Description of what the tool does',
5
category: 'custom',
6
provider: 'custom',
7
input_schema: {
8
type: 'object',
9
properties: {
10
// Define input parameters
11
},
12
required: ['required_param']
13
},
14
output_schema: {
15
type: 'object',
16
properties: {
17
// Define output format
18
}
19
},
20
implementation: async (parameters, context) => {
21
// Custom tool logic
22
return result;
23
}
24
}
```
## Creating custom tools
[Section titled “Creating custom tools”](#creating-custom-tools)
### Basic custom tool
[Section titled “Basic custom tool”](#basic-custom-tool)
Here’s a simple custom tool that sends a welcome email:
```javascript
1
const sendWelcomeEmail = {
2
name: 'send_welcome_email',
3
display_name: 'Send Welcome Email',
4
description: 'Send a personalized welcome email to new users',
5
category: 'communication',
6
provider: 'custom',
7
input_schema: {
8
type: 'object',
9
properties: {
10
user_name: {
11
type: 'string',
12
description: 'Name of the new user'
13
},
14
user_email: {
15
type: 'string',
16
format: 'email',
17
description: 'Email address of the new user'
18
},
19
company_name: {
20
type: 'string',
21
description: 'Name of the company'
22
}
23
},
24
required: ['user_name', 'user_email', 'company_name']
25
},
26
output_schema: {
27
type: 'object',
28
properties: {
29
message_id: {
30
type: 'string',
31
description: 'ID of the sent email'
32
},
33
status: {
34
type: 'string',
35
enum: ['sent', 'failed'],
36
description: 'Status of the email'
37
}
38
}
39
},
40
implementation: async (parameters, context) => {
41
const { user_name, user_email, company_name } = parameters;
42
43
// Generate personalized email content
44
const emailBody = `
45
Welcome to ${company_name}, ${user_name}!
46
47
We're excited to have you join our team. Here are some next steps:
48
49
1. Complete your profile setup
50
2. Join our Slack workspace
51
3. Schedule a meeting with your manager
52
53
If you have any questions, don't hesitate to reach out!
54
55
Best regards,
56
The ${company_name} Team
57
`;
58
59
// Send email using standard email tool
60
const result = await context.tools.execute({
61
tool: 'send_email',
62
parameters: {
63
to: [user_email],
64
subject: `Welcome to ${company_name}!`,
65
body: emailBody
66
}
67
});
68
69
return {
70
message_id: result.message_id,
71
status: result.status === 'sent' ? 'sent' : 'failed'
72
};
73
}
74
};
```
### Multi-step workflow tool
[Section titled “Multi-step workflow tool”](#multi-step-workflow-tool)
Create a tool that combines multiple operations:
```javascript
1
const createProjectWorkflow = {
2
name: 'create_project_workflow',
3
display_name: 'Create Project Workflow',
4
description: 'Create a complete project setup with Jira project, Slack channel, and team notifications',
5
category: 'project_management',
6
provider: 'custom',
7
input_schema: {
8
type: 'object',
9
properties: {
10
project_name: {
11
type: 'string',
12
description: 'Name of the project'
13
},
14
project_key: {
15
type: 'string',
16
description: 'Project key for Jira'
17
},
18
team_members: {
19
type: 'array',
20
items: { type: 'string', format: 'email' },
21
description: 'Team member email addresses'
22
},
23
project_description: {
24
type: 'string',
25
description: 'Project description'
26
}
27
},
28
required: ['project_name', 'project_key', 'team_members']
29
},
30
output_schema: {
31
type: 'object',
32
properties: {
33
jira_project_id: { type: 'string' },
34
slack_channel_id: { type: 'string' },
35
notifications_sent: { type: 'number' }
36
}
37
},
38
implementation: async (parameters, context) => {
39
const { project_name, project_key, team_members, project_description } = parameters;
40
41
try {
42
// Step 1: Create Jira project
43
const jiraProject = await context.tools.execute({
44
tool: 'create_jira_project',
45
parameters: {
46
key: project_key,
47
name: project_name,
48
description: project_description,
49
project_type: 'software'
50
}
51
});
52
53
// Step 2: Create Slack channel
54
const slackChannel = await context.tools.execute({
55
tool: 'create_channel',
56
parameters: {
57
name: `${project_key.toLowerCase()}-team`,
58
topic: `Discussion for ${project_name}`,
59
is_private: false
60
}
61
});
62
63
// Step 3: Send notifications to team members
64
let notificationCount = 0;
65
for (const member of team_members) {
66
try {
67
await context.tools.execute({
68
tool: 'send_email',
69
parameters: {
70
to: [member],
71
subject: `New Project: ${project_name}`,
72
body: `
73
You've been added to the new project "${project_name}".
74
75
Jira Project: ${jiraProject.project_url}
76
Slack Channel: #${slackChannel.channel_name}
77
78
Please join the Slack channel to start collaborating!
79
`
80
}
81
});
82
notificationCount++;
83
} catch (error) {
84
console.error(`Failed to send notification to ${member}:`, error);
85
}
86
}
87
88
// Step 4: Post welcome message to Slack channel
89
await context.tools.execute({
90
tool: 'send_message',
91
parameters: {
92
channel: `#${slackChannel.channel_name}`,
93
text: `<� Welcome to ${project_name}! This channel is for project discussion and updates.`
94
}
95
});
96
97
return {
98
jira_project_id: jiraProject.project_id,
99
slack_channel_id: slackChannel.channel_id,
100
notifications_sent: notificationCount
101
};
102
103
} catch (error) {
104
throw new Error(`Project creation failed: ${error.message}`);
105
}
106
}
107
};
```
### Data processing tool
[Section titled “Data processing tool”](#data-processing-tool)
Create a tool that processes and analyzes data:
```javascript
1
const generateTeamReport = {
2
name: 'generate_team_report',
3
display_name: 'Generate Team Report',
4
description: 'Generate a comprehensive team performance report from multiple sources',
5
category: 'analytics',
6
provider: 'custom',
7
input_schema: {
8
type: 'object',
9
properties: {
10
team_members: {
11
type: 'array',
12
items: { type: 'string', format: 'email' },
13
description: 'Team member email addresses'
14
},
15
start_date: {
16
type: 'string',
17
format: 'date',
18
description: 'Report start date'
19
},
20
end_date: {
21
type: 'string',
22
format: 'date',
23
description: 'Report end date'
24
},
25
include_calendar: {
26
type: 'boolean',
27
default: true,
28
description: 'Include calendar analysis'
29
}
30
},
31
required: ['team_members', 'start_date', 'end_date']
32
},
33
output_schema: {
34
type: 'object',
35
properties: {
36
report_url: { type: 'string' },
37
summary: { type: 'object' },
38
sent_to: { type: 'array', items: { type: 'string' } }
39
}
40
},
41
implementation: async (parameters, context) => {
42
const { team_members, start_date, end_date, include_calendar } = parameters;
43
44
// Fetch Jira issues assigned to team members
45
const jiraIssues = await context.tools.execute({
46
tool: 'fetch_issues',
47
parameters: {
48
jql: `assignee in (${team_members.join(',')}) AND created >= ${start_date} AND created <= ${end_date}`,
49
fields: ['summary', 'status', 'assignee', 'created', 'resolved']
50
}
51
});
52
53
// Fetch calendar events if requested
54
let calendarData = null;
55
if (include_calendar) {
56
calendarData = await context.tools.execute({
57
tool: 'fetch_events',
58
parameters: {
59
start_date: start_date,
60
end_date: end_date,
61
attendees: team_members
62
}
63
});
64
}
65
66
// Process and analyze data
67
const report = {
68
period: { start_date, end_date },
69
team_size: team_members.length,
70
issues: {
71
total: jiraIssues.issues.length,
72
completed: jiraIssues.issues.filter(i => i.status === 'Done').length,
73
in_progress: jiraIssues.issues.filter(i => i.status === 'In Progress').length
74
},
75
meetings: calendarData ? {
76
total: calendarData.events.length,
77
hours: calendarData.events.reduce((acc, event) => acc + event.duration, 0)
78
} : null
79
};
80
81
// Generate HTML report
82
const htmlReport = `
83
84
Team Report - ${start_date} to ${end_date}
85
86
Team Performance Report
87
Summary
88
Team Size: ${report.team_size}
89
Total Issues: ${report.issues.total}
90
Completed Issues: ${report.issues.completed}
91
In Progress: ${report.issues.in_progress}
92
${report.meetings ? `Total Meetings: ${report.meetings.total}
` : ''}
93
94
95
`;
96
97
// Send report via email
98
const emailResults = await Promise.all(
99
team_members.map(member =>
100
context.tools.execute({
101
tool: 'send_email',
102
parameters: {
103
to: [member],
104
subject: `Team Report - ${start_date} to ${end_date}`,
105
html_body: htmlReport
106
}
107
})
108
)
109
);
110
111
return {
112
report_url: 'Generated and sent via email',
113
summary: report,
114
sent_to: team_members.filter((_, index) => emailResults[index].status === 'sent')
115
};
116
}
117
};
```
## Registering custom tools
[Section titled “Registering custom tools”](#registering-custom-tools)
### Using the API
[Section titled “Using the API”](#using-the-api)
Register your custom tools with Agent Auth:
* JavaScript
```javascript
1
// Register a custom tool
2
const registeredTool = await agentConnect.tools.register({
3
...sendWelcomeEmail,
4
organization_id: 'your_org_id'
5
});
6
7
console.log('Tool registered:', registeredTool.id);
```
* Python
```python
1
# Register a custom tool
2
registered_tool = agent_connect.tools.register(
3
**send_welcome_email,
4
organization_id='your_org_id'
5
)
6
7
print(f'Tool registered: {registered_tool.id}')
```
* cURL
```bash
1
curl -X POST "${SCALEKIT_BASE_URL}/v1/connect/tools/custom" \
2
-H "Authorization: Bearer ${SCALEKIT_CLIENT_SECRET}" \
3
-H "Content-Type: application/json" \
4
-d '{
5
"name": "send_welcome_email",
6
"display_name": "Send Welcome Email",
7
"description": "Send a personalized welcome email to new users",
8
"category": "communication",
9
"provider": "custom",
10
"input_schema": {...},
11
"output_schema": {...},
12
"implementation": "async (parameters, context) => {...}"
13
}'
```
### Using the dashboard
[Section titled “Using the dashboard”](#using-the-dashboard)
1. Navigate to **Tools** in your Agent Auth dashboard
2. Click **Create Custom Tool**
3. Fill in the tool definition form
4. Test the tool with sample parameters
5. Save and activate the tool
## Tool context and utilities
[Section titled “Tool context and utilities”](#tool-context-and-utilities)
The `context` object provides access to:
### Standard tools
[Section titled “Standard tools”](#standard-tools)
Execute any standard Agent Auth tool:
```javascript
1
// Execute standard tools
2
const result = await context.tools.execute({
3
tool: 'send_email',
4
parameters: { ... }
5
});
6
7
// Execute with specific connected account
8
const result = await context.tools.execute({
9
connected_account_id: 'specific_account',
10
tool: 'send_email',
11
parameters: { ... }
12
});
```
### Connected accounts
[Section titled “Connected accounts”](#connected-accounts)
Access connected account information:
```javascript
1
// Get connected account details
2
const account = await context.accounts.get(accountId);
3
4
// List accounts for a user
5
const accounts = await context.accounts.list({
6
identifier: 'user_123',
7
provider: 'gmail'
8
});
```
### Utilities
[Section titled “Utilities”](#utilities)
Access utility functions:
```javascript
1
// Generate unique IDs
2
const id = context.utils.generateId();
3
4
// Format dates
5
const formatted = context.utils.formatDate(date, 'YYYY-MM-DD');
6
7
// Validate email
8
const isValid = context.utils.isValidEmail(email);
9
10
// HTTP requests
11
const response = await context.utils.httpRequest({
12
url: 'https://api.example.com/data',
13
method: 'GET',
14
headers: { 'Authorization': 'Bearer token' }
15
});
```
### Error handling
[Section titled “Error handling”](#error-handling)
Throw structured errors:
```javascript
1
// Throw validation error
2
throw new context.errors.ValidationError('Invalid email format');
3
4
// Throw business logic error
5
throw new context.errors.BusinessLogicError('User not found');
6
7
// Throw external API error
8
throw new context.errors.ExternalAPIError('GitHub API returned 500');
```
## Testing custom tools
[Section titled “Testing custom tools”](#testing-custom-tools)
### Unit testing
[Section titled “Unit testing”](#unit-testing)
Test custom tools in isolation:
```javascript
1
// Mock context for testing
2
const mockContext = {
3
tools: {
4
execute: jest.fn().mockResolvedValue({
5
message_id: 'test_msg_123',
6
status: 'sent'
7
})
8
},
9
utils: {
10
generateId: () => 'test_id_123',
11
formatDate: (date, format) => '2024-01-15'
12
}
13
};
14
15
// Test custom tool
16
const result = await sendWelcomeEmail.implementation({
17
user_name: 'John Doe',
18
user_email: 'john@example.com',
19
company_name: 'Acme Corp'
20
}, mockContext);
21
22
expect(result.status).toBe('sent');
23
expect(mockContext.tools.execute).toHaveBeenCalledWith({
24
tool: 'send_email',
25
parameters: expect.objectContaining({
26
to: ['john@example.com'],
27
subject: 'Welcome to Acme Corp!'
28
})
29
});
```
### Integration testing
[Section titled “Integration testing”](#integration-testing)
Test with real Agent Auth:
```javascript
1
// Test custom tool with real connections
2
const testResult = await agentConnect.tools.execute({
3
connected_account_id: 'test_gmail_account',
4
tool: 'send_welcome_email',
5
parameters: {
6
user_name: 'Test User',
7
user_email: 'test@example.com',
8
company_name: 'Test Company'
9
}
10
});
11
12
console.log('Test result:', testResult);
```
## Best practices
[Section titled “Best practices”](#best-practices)
### Tool design
[Section titled “Tool design”](#tool-design)
* **Single responsibility**: Each tool should have a clear, single purpose
* **Consistent naming**: Use descriptive, consistent naming conventions
* **Clear documentation**: Provide detailed descriptions and examples
* **Error handling**: Implement comprehensive error handling
* **Input validation**: Validate all input parameters
### Performance optimization
[Section titled “Performance optimization”](#performance-optimization)
* **Parallel execution**: Use Promise.all() for independent operations
* **Caching**: Cache frequently accessed data
* **Batch operations**: Group similar operations together
* **Timeout handling**: Set appropriate timeouts for external calls
### Security considerations
[Section titled “Security considerations”](#security-considerations)
* **Input sanitization**: Sanitize all user inputs
* **Permission checks**: Verify user permissions before execution
* **Sensitive data**: Handle sensitive data securely
* **Rate limiting**: Implement rate limiting for resource-intensive operations
## Custom tool examples
[Section titled “Custom tool examples”](#custom-tool-examples)
### Slack notification tool
[Section titled “Slack notification tool”](#slack-notification-tool)
```javascript
1
const sendSlackNotification = {
2
name: 'send_slack_notification',
3
display_name: 'Send Slack Notification',
4
description: 'Send formatted notifications to Slack with optional mentions',
5
category: 'communication',
6
provider: 'custom',
7
input_schema: {
8
type: 'object',
9
properties: {
10
channel: { type: 'string' },
11
message: { type: 'string' },
12
severity: { type: 'string', enum: ['info', 'warning', 'error'] },
13
mentions: { type: 'array', items: { type: 'string' } }
14
},
15
required: ['channel', 'message']
16
},
17
output_schema: {
18
type: 'object',
19
properties: {
20
message_ts: { type: 'string' },
21
permalink: { type: 'string' }
22
}
23
},
24
implementation: async (parameters, context) => {
25
const { channel, message, severity = 'info', mentions = [] } = parameters;
26
27
const colors = {
28
info: 'good',
29
warning: 'warning',
30
error: 'danger'
31
};
32
33
const mentionText = mentions.length > 0 ?
34
`${mentions.map(m => `<@${m}>`).join(' ')} ` : '';
35
36
return await context.tools.execute({
37
tool: 'send_message',
38
parameters: {
39
channel,
40
text: `${mentionText}${message}`,
41
attachments: [
42
{
43
color: colors[severity],
44
text: message,
45
ts: Math.floor(Date.now() / 1000)
46
}
47
]
48
}
49
});
50
}
51
};
```
### Calendar scheduling tool
[Section titled “Calendar scheduling tool”](#calendar-scheduling-tool)
```javascript
1
const scheduleTeamMeeting = {
2
name: 'schedule_team_meeting',
3
display_name: 'Schedule Team Meeting',
4
description: 'Find available time slots and schedule team meetings',
5
category: 'scheduling',
6
provider: 'custom',
7
input_schema: {
8
type: 'object',
9
properties: {
10
attendees: { type: 'array', items: { type: 'string' } },
11
duration: { type: 'number', minimum: 15 },
12
preferred_times: { type: 'array', items: { type: 'string' } },
13
meeting_title: { type: 'string' },
14
meeting_description: { type: 'string' }
15
},
16
required: ['attendees', 'duration', 'meeting_title']
17
},
18
output_schema: {
19
type: 'object',
20
properties: {
21
event_id: { type: 'string' },
22
scheduled_time: { type: 'string' },
23
attendees_notified: { type: 'number' }
24
}
25
},
26
implementation: async (parameters, context) => {
27
const { attendees, duration, preferred_times, meeting_title, meeting_description } = parameters;
28
29
// Find available time slots
30
const availableSlots = await context.tools.execute({
31
tool: 'find_available_slots',
32
parameters: {
33
attendees,
34
duration,
35
preferred_times: preferred_times || []
36
}
37
});
38
39
if (availableSlots.length === 0) {
40
throw new context.errors.BusinessLogicError('No available time slots found');
41
}
42
43
// Schedule the meeting at the first available slot
44
const selectedSlot = availableSlots[0];
45
const event = await context.tools.execute({
46
tool: 'create_event',
47
parameters: {
48
title: meeting_title,
49
description: meeting_description,
50
start_time: selectedSlot.start_time,
51
end_time: selectedSlot.end_time,
52
attendees
53
}
54
});
55
56
return {
57
event_id: event.event_id,
58
scheduled_time: selectedSlot.start_time,
59
attendees_notified: attendees.length
60
};
61
}
62
};
```
## Versioning and deployment
[Section titled “Versioning and deployment”](#versioning-and-deployment)
### Version management
[Section titled “Version management”](#version-management)
Version your custom tools for backward compatibility:
```javascript
1
const toolV2 = {
2
...originalTool,
3
version: '2.0.0',
4
// Updated implementation
5
};
6
7
// Deploy new version
8
await agentConnect.tools.register(toolV2);
9
10
// Deprecate old version
11
await agentConnect.tools.deprecate(originalTool.name, '1.0.0');
```
### Deployment strategies
[Section titled “Deployment strategies”](#deployment-strategies)
* **Blue-green deployment**: Deploy new version alongside old version
* **Canary deployment**: Gradually roll out to subset of users
* **Feature flags**: Use feature flags to control tool availability
* **Rollback strategy**: Plan for quick rollback if issues arise
Note
**Ready to build?** Start with simple custom tools and gradually add complexity. Test thoroughly before deploying to production, and consider the impact on your users when making changes.
Custom tools unlock the full potential of Agent Auth by allowing you to create specialized workflows that perfectly match your business needs. With proper design, testing, and deployment practices, you can build powerful tools that enhance your team’s productivity and streamline complex operations.
---
# DOCUMENT BOUNDARY
---
# User authentication flow
> Learn how Scalekit routes users through authentication based on login method and organization SSO policies.
The user’s authentication journey on the hosted login page can differ based on the **login method** they choose and the **organization policies** configured in Scalekit.
## Organization policies
[Section titled “Organization policies”](#organization-policies)
Organizations can enforce Enterprise SSO for their users. An organization must create an enabled [SSO connection](/authenticate/auth-methods/enterprise-sso/) and add [organization domains](/authenticate/auth-methods/enterprise-sso/#identify-and-enforce-sso-for-organization-users).
Scalekit uses **Home Realm Discovery (HRD)** to determine whether a user’s email domain matches a configured organization domain. When a match is found, the user is routed to that organization’s SSO identity provider.
**Examples**
* A user tries to log in as `user@samecorp.com` on the hosted login page. If `samecorp.com` is registered as an organization domain with SSO enabled, the user is redirected to that organization’s IdP to complete authentication.
* A user tries to log in with Google as `user@samecorp.com` on the hosted login page. If `samecorp.com` is registered as an organization domain with SSO enabled, the user is redirected to that organization’s IdP after returning from Google.
## Login method–specific behavior
[Section titled “Login method–specific behavior”](#login-methodspecific-behavior)
Scalekit allows users to choose different login methods on the hosted login page. The timing of organization domain checks differs slightly by method, but the rules remain consistent.
### Social login
[Section titled “Social login”](#social-login)
* User authenticates with a social IdP (e.g., Google, GitHub).
* Scalekit evaluates the user’s email after social auth completes.
* Home Realm Discovery (HRD) checks whether the email domain matches an organization domain.
* **Domain match:** User is redirected to the organization’s SSO IdP.
* **No match:** Authentication completes.
This ensures that enterprise users must complete SSO authentication even if they initially choose social login.
### Passkey login
[Section titled “Passkey login”](#passkey-login)
* User authenticates using a passkey.
* Authentication succeeds immediately.
* Scalekit performs Home Realm Discovery (HRD) to check the email domain.
* **Domain match:** User is redirected to SSO.
* **No match:** Authentication completes.
Passkeys authenticate the user, but do not override organization SSO policy.
### Email-based login
[Section titled “Email-based login”](#email-based-login)
* User enters their email address.
* Home Realm Discovery (HRD) runs **before authentication** to check the email domain.
* **Domain match:** User is redirected to SSO.
* **No match:** Scalekit performs OTP or magic link verification, then authentication completes.
### Authentication flow
[Section titled “Authentication flow”](#authentication-flow)
This diagram shows the different variations of user’s authentication journey on the hosted login page.
***
## Enterprise SSO Trust model
[Section titled “Enterprise SSO Trust model”](#enterprise-sso-trust-model)
Most enterprise identity providers (IdPs) like Okta or Microsoft Entra do not prove that a user actually controls the email inbox they sign in with. They only assert an email address in the SAML/OIDC token. Because of this, when a user logs in via Enterprise SSO, Scalekit does not automatically treat that SSO connection as a trusted source of email ownership.
Since Scalekit cannot be sure that the SSO user truly owns the email address, the user is taken through an email ownership check (magic link or OTP) to prove control of that inbox. After the user successfully verifies their email, that SSO connection is marked as a verified channel for that specific user, and they do not need to verify email ownership again on subsequent logins via the same connection.
If you want an Enterprise SSO connection to be treated as a trusted provider for a specific domain, you can assign one or more domains to the organization. Then, for users logging in via that Enterprise SSO connection whose email address matches one of the configured domains, Scalekit skips additional email ownership verification.
| SSO trust case | Example | Result |
| -------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| Trusted SSO | Org has added `acmecorp.com` in organization domain. User authenticates as `user@acmecorp.com` with organization SSO. | Email ownership trusted |
| Untrusted SSO | Org has added `acmecorp.com` in organization domain and user authenticates as `user@foocorp.com` with organization SSO. | Email ownership not trusted → Additional verification required |
***
## Forcing SSO from your application
[Section titled “Forcing SSO from your application”](#forcing-sso-from-your-application)
Your app can override Home Realm Discovery (HRD) by passing `organization_id` or `connection_id` in the authentication request ↗ to Scalekit. When you do this:
* Scalekit skips HRD and redirects the user directly to the specified SSO IdP.
* After SSO authentication completes, Scalekit checks whether the user’s email domain matches one of the organization domains configured on that SSO connection.
* **Domain match**: authentication completes.
* **No match**: Scalekit requires additional verification (OTP or magic link) before completing authentication.
## IdP‑initiated SSO
[Section titled “IdP‑initiated SSO”](#idpinitiated-sso)
In IdP‑initiated SSO, authentication starts at the identity provider instead of your application or the hosted login page. After the IdP authenticates the user and redirects to Scalekit, Scalekit evaluates email ownership trust:
* If the user’s email domain matches one of the organization domains configured on the SSO connection, authentication completes.
* If the email domain does not match, Scalekit requires additional verification (OTP or magic link) before completing authentication.
This workflow ensures IdP‑initiated flows follow the same email ownership and trust guarantees as app‑initiated SSO
***
## Account linking
[Section titled “Account linking”](#account-linking)
### What happens
[Section titled “What happens”](#what-happens)
Scalekit maintains a single user record per email address. For example, if a user first authenticates with passwordless login (magic link/OTP) and later uses Google or Enterprise SSO, Scalekit links both identities to the same user record. These identities are stored on the user object for your app to read if needed. This avoids duplicate users when people switch authentication methods.
### Why it is safe
[Section titled “Why it is safe”](#why-it-is-safe)
Scalekit only treats an SSO IdP as a trusted source of email ownership when:
* the authenticated email domain matches one of the organization domains configured on the SSO connection, or
* the user has previously proven email ownership via magic link or OTP.
Because the organization has proven domain ownership, and/or the user has proven inbox control, emails from that SSO connection are treated as valid. This prevents attackers from linking identities unless email ownership has been verified through trusted mechanisms.
---
# DOCUMENT BOUNDARY
---
# Implement enterprise SSO
> How to implement enterprise SSO for your application
Enterprise single sign-on (SSO) enables users to authenticate using their organization’s identity provider (IdP), such as Okta, Azure AD, or Google Workspace. [After completing the quickstart](/authenticate/fsa/quickstart/), follow this guide to implement SSO for an organization, streamline admin onboarding, enforce login requirements, and validate your configuration.
1. ## Enable SSO for the organization
[Section titled “Enable SSO for the organization”](#enable-sso-for-the-organization)
When a user signs up for your application, Scalekit automatically creates an organization and assigns an admin role to the user. Provide an option in your user interface to enable SSO for the organization or workspace.
Here’s how you can do that with Scalekit. Use the following SDK method to activate SSO for the organization:
* Node.js
Enable SSO
```javascript
const settings = {
features: [
{
name: 'sso',
enabled: true,
}
],
};
await scalekit.organization.updateOrganizationSettings(
'', // Get this from the idToken or accessToken
settings
);
```
* Python
Enable SSO
```python
settings = [
{
"name": "sso",
"enabled": True
}
]
scalekit.organization.update_organization_settings(
organization_id='', # Get this from the idToken or accessToken
settings=settings
)
```
* Java
Enable SSO
```java
OrganizationSettingsFeature featureSSO = OrganizationSettingsFeature.newBuilder()
.setName("sso")
.setEnabled(true)
.build();
updatedOrganization = scalekitClient.organizations()
.updateOrganizationSettings(organizationId, List.of(featureSSO));
```
* Go
Enable SSO
```go
settings := OrganizationSettings{
Features: []Feature{
{
Name: "sso",
Enabled: true,
},
},
}
organization, err := sc.Organization().UpdateOrganizationSettings(ctx, organizationId, settings)
if err != nil {
// Handle error
}
```
You can also enable this from the [organization settings](/authenticate/fsa/user-management-settings/) in the Scalekit dashboard.
2. ## Enable admin portal for enterprise customer onboarding
[Section titled “Enable admin portal for enterprise customer onboarding”](#enable-admin-portal-for-enterprise-customer-onboarding)
After SSO is enabled for that organization, provide a method for configuring a SSO connection with the organization’s identity provider.
Scalekit offers two primary approaches:
* Generate a link to the admin portal from the Scalekit dashboard and share it with organization admins via your usual channels.
* Or embed the admin portal in your application in an inline frame so administrators can configure their IdP without leaving your app.
[See how to onboard enterprise customers ](/sso/guides/onboard-enterprise-customers/)
3. ## Identify and enforce SSO for organization users
[Section titled “Identify and enforce SSO for organization users”](#identify-and-enforce-sso-for-organization-users)
Administrators typically register organization-owned domains through the admin portal. When a user attempts to sign in with an email address matching a registered domain, they are automatically redirected to their organization’s designated identity provider for authentication.
**Organization domains** automatically route users to the correct SSO connection based on their email address. When a user signs in with an email domain that matches a registered organization domain, Scalekit redirects them to that organization’s SSO provider and enforces SSO login.
For example, if an organization registers `megacorp.org`, any user signing in with an `joe@megacorp.org` email address is redirected to Megacorp’s SSO provider.

Navigate to **Dashboard > Organizations** and select the target organization > **Overview** > **Organization Domains** section to register organization domains.
4. ## Test your SSO integration
[Section titled “Test your SSO integration”](#test-your-sso-integration)
Scalekit offers a “Test Organization” feature that enables SSO flow validation without requiring test accounts from your customers’ identity providers.
To quickly test the integration, enter an email address using the domains `joe@example.com` or `jane@example.org`. This will trigger a redirect to the IdP simulator, which serves as the test organization’s identity provider for authentication.
For a comprehensive step-by-step walkthrough, refer to the [Test SSO integration guide](/sso/guides/test-sso/).
---
# DOCUMENT BOUNDARY
---
# Add passkeys login method
> Enable passkey authentication for your users
Passkeys replace passwords with biometric authentication (fingerprint, face recognition) or device PINs. Built on FIDO® standards (WebAuthn and CTAP), passkeys offer superior security by eliminating phishing and credential stuffing vulnerabilities, while also providing a seamless one-tap login experience. Unlike traditional authentication methods, passkeys sync across devices, removing the need for multiple enrollments and providing better recovery options when devices are lost.
Your [existing Scalekit integration](/authenticate/fsa/quickstart) already supports passkeys. To implement, enable passkeys in the Scalekit dashboard and leverage Scalekit’s built-in user passkey registration functionality.
1. ## Enable passkeys in the Scalekit dashboard
[Section titled “Enable passkeys in the Scalekit dashboard”](#enable-passkeys-in-the-scalekit-dashboard)
Go to Scalekit Dashboard > Authentication > Auth methods > Passkeys and click “Enable”

2. ## Manage passkey registration
[Section titled “Manage passkey registration”](#manage-passkey-registration)
Let users manage passkeys just by redirecting them to Scalekit from your app (usually through a button in your app that says “Manage passkeys”), or building your own UI.
#### Using Scalekit UI
[Section titled “Using Scalekit UI”](#using-scalekit-ui)
To enable users to register and manage their passkeys, redirect them to the Scalekit passkey registration page.

Construct the URL by appending `/ui/profile/passkeys` to your Scalekit environment URL
Passkey Registration URL
```js
/ui/profile/passkeys
```
This opens a page where users can:
* Register new passkeys
* Remove existing passkeys
* View their registered passkeys
Note
Scalekit registers & authenticates user’s passkeys through the browser’s native passkey API. This API prompts users to authenticate with device-supported passkeys — such as fingerprint, PIN, or password managers.
#### In your own UI
[Section titled “In your own UI”](#in-your-own-ui)
If you prefer to create a custom user interface for passkey management, Scalekit offers comprehensive APIs that enable you to build a personalized experience. These APIs allow you to list registered passkeys, rename them, and remove them entirely. However registration of passkeys is only supported through the Scalekit UI.
* Node.js
List user's passkeys
```js
// : fetch from Access Token or ID Token after identity verification
const res = await fetch(
'/api/v1/webauthn/credentials?user_id=',
{ headers: { Authorization: 'Bearer ' } }
);
const data = await res.json();
console.log(data);
```
Rename a passkey
```js
// : obtained from list response (id of each passkey)
await fetch('/api/v1/webauthn/credentials/', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer '
},
body: JSON.stringify({ display_name: '' })
});
```
Remove a passkey
```js
// : obtained from list response (id of each passkey)
await fetch('/api/v1/webauthn/credentials/', {
method: 'DELETE',
headers: { Authorization: 'Bearer ' }
});
```
* Python
List user's passkeys
```python
import requests
# : fetch from access token or ID token after identity verification
r = requests.get(
'/api/v1/webauthn/credentials',
params={'user_id': ''},
headers={'Authorization': 'Bearer '}
)
print(r.json())
```
Rename a passkey
```python
import requests
# : obtained from list response (id of each passkey)
requests.patch(
'/api/v1/webauthn/credentials/',
json={'display_name': ''},
headers={'Authorization': 'Bearer '}
)
```
Remove a passkey
```python
import requests
# : obtained from list response (id of each passkey)
requests.delete(
'/api/v1/webauthn/credentials/',
headers={'Authorization': 'Bearer '}
)
```
* Java
List user's passkeys
```java
var client = java.net.http.HttpClient.newHttpClient();
// : fetch from Access Token or ID Token after identity verification
var req = java.net.http.HttpRequest.newBuilder(
java.net.URI.create("/api/v1/webauthn/credentials?user_id=")
)
.header("Authorization", "Bearer ")
.GET().build();
var res = client.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
```
Rename a passkey
```java
var client = java.net.http.HttpClient.newHttpClient();
var body = "{\"display_name\":\"\"}";
// : obtained from list response (id of each passkey)
var req = java.net.http.HttpRequest.newBuilder(
java.net.URI.create("/api/v1/webauthn/credentials/")
)
.header("Authorization", "Bearer ")
.header("Content-Type","application/json")
.method("PATCH", java.net.http.HttpRequest.BodyPublishers.ofString(body))
.build();
client.send(req, java.net.http.HttpResponse.BodyHandlers.discarding());
```
Remove a passkey
```java
var client = java.net.http.HttpClient.newHttpClient();
// : obtained from list response (id of each passkey)
var req = java.net.http.HttpRequest.newBuilder(
java.net.URI.create("/api/v1/webauthn/credentials/")
)
.header("Authorization", "Bearer ")
.DELETE().build();
client.send(req, java.net.http.HttpResponse.BodyHandlers.discarding());
```
* Go
List user's passkeys
```go
// imports: net/http, io, fmt
// : fetch from access token or ID token after identity verification
req, _ := http.NewRequest("GET", "/api/v1/webauthn/credentials?user_id=", nil)
req.Header.Set("Authorization", "Bearer ")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
```
Rename a passkey
```go
// imports: net/http, bytes
payload := bytes.NewBufferString(`{"display_name":""}`)
// : obtained from list response (id of each passkey)
req, _ := http.NewRequest("PATCH", "/api/v1/webauthn/credentials/", payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer ")
http.DefaultClient.Do(req)
```
Remove a passkey
```go
// imports: net/http
// : obtained from list response (id of each passkey)
req, _ := http.NewRequest("DELETE", "/api/v1/webauthn/credentials/", nil)
req.Header.Set("Authorization", "Bearer ")
http.DefaultClient.Do(req)
```
Note
All API requests require an access token obtained via the OAuth 2.0 client credentials flow. Follow [Authenticate with the Scalekit API](/guides/authenticate-scalekit-api), then replace `` in the examples below.
3. ## Users can log in with passkeys
[Section titled “Users can log in with passkeys”](#users-can-log-in-with-passkeys)
Users who have registered passkeys can log in with them.
This time when login page shows, users can select “Passkey” as the authentication method.

During sign-up, you’ll continue to use established authentication methods like [verification codes, magic links](/authenticate/auth-methods/passwordless/) or [social logins](/authenticate/auth-methods/social-logins/). Once a user is registered, they can then add passkeys as an additional, convenient login option.
---
# DOCUMENT BOUNDARY
---
# Sign in with magic link or Email OTP
> Enable passwordless sign-in with email verification codes or magic links
Configure Magic Link & OTP to enable passwordless authentication for your application. After completing the [quickstart guide](/authenticate/fsa/quickstart/), set up email verification codes or magic links so users can sign in without passwords.
Switch between those passwordless methods without modifying any code:
| Method | How it works | Best for |
| ------------------------------ | ---------------------------------------------------------------- | -------------------------------------------- |
| Verification code | Users receive a one-time code via email and enter it in your app | Applications requiring explicit verification |
| Magic link | Users click a link in their email to authenticate | Quick, frictionless sign-in |
| Magic link + Verification code | Users choose either method | Maximum flexibility and user choice |
## Configure magic link or OTP
[Section titled “Configure magic link or OTP”](#configure-magic-link-or-otp)
In the Scalekit dashboard, go to **Authentication > Auth methods > Magic Link & OTP**

1. ### Select authentication method
[Section titled “Select authentication method”](#select-authentication-method)
Choose one of three methods:
* **Verification code** - Users enter a 6-digit code sent to their email
* **Magic link** - Users click a link in their email to authenticate
* **Magic link + Verification code** - Users can choose either method
2. ### Set expiry period
[Section titled “Set expiry period”](#set-expiry-period)
Configure how long verification codes and magic links remain valid:
* **Default**: 300 seconds (5 minutes)
* **Range**: 60 to 3600 seconds
* **Recommendation**: 300 seconds balances security and usability
Note
While shorter expiry periods enhance security by reducing the window for potential unauthorized access, they can negatively impact user experience, especially with shorter email-to-input times. Conversely, longer periods provide more convenience but increase the risk of credential misuse if intercepted.
## Enforce same browser origin
[Section titled “Enforce same browser origin”](#enforce-same-browser-origin)
When enforcing same browser origin, users are required to complete magic link authentication within the same browser where they initiated the login process. This security feature is particularly recommended for applications dealing with sensitive data or financial transactions, as it adds an extra layer of protection against potential unauthorized access attempts.
**Example scenario**: A healthcare app where a user requests a magic link on their laptop. If someone intercepts the email and tries to open it on a different device, the authentication fails.
## Regenerate credentials on resend
[Section titled “Regenerate credentials on resend”](#regenerate-credentials-on-resend)
When a user requests a new Magic Link or Email OTP, the system generates a fresh code or link while automatically invalidating the previous one. This approach is recommended for all applications as a critical security measure to prevent potential misuse of compromised credentials.
**Example scenario**: A user requests a verification code but doesn’t receive it. They request a new code. With this setting enabled, the first code becomes invalid, preventing unauthorized access if the original email was intercepted.
---
# DOCUMENT BOUNDARY
---
# Add social login to your app
> Implement authentication with Google, Microsoft, GitHub, and other social providers
First, complete the [quickstart guide](/authenticate/fsa/quickstart/) to integrate Scalekit auth into your application. Scalekit natively supports OAuth 2.0, enabling you to easily configure social login providers that will automatically appear as authentication options on your login page.
1. ## Configure social login providers
[Section titled “Configure social login providers”](#configure-social-login-providers)
Google login is pre-configured in all development environments for simplified testing. You can integrate additional social login providers by setting up your own connection credentials with each provider.
Navigate to **Authentication** > **Auth Methods** > **Social logins** in your dashboard to configure these settings
### Google
Enable users to sign in with their Google accounts using OAuth 2.0
[Setup →](/guides/integrations/social-connections/google)
### GitHub
Allow users to authenticate using their GitHub credentials
[Setup →](/guides/integrations/social-connections/github)
### Microsoft
Integrate Microsoft accounts for seamless user authentication
[Setup →](/guides/integrations/social-connections/microsoft)
### GitLab
Enable GitLab-based authentication for your application
[Setup →](/guides/integrations/social-connections/gitlab)
### LinkedIn
Let users sign in with their LinkedIn accounts using OAuth 2.0
[Setup →](/guides/integrations/social-connections/linkedin)
### Salesforce
Enable Salesforce-based authentication for your application
[Setup →](/guides/integrations/social-connections/salesforce)
2. ## Test the social connection
[Section titled “Test the social connection”](#test-the-social-connection)
After configuration, test the social connection by clicking on “Test Connection” in the dashboard. You will be redirected to the provider’s consent screen to authorize access. A summary table will show the information that will be sent to your app.

## Access social login options on your login page
[Section titled “Access social login options on your login page”](#access-social-login-options-on-your-login-page)
Your application now supports social logins.
Begin the [login process](/authenticate/fsa/implement-login/) to experience the available social login options. Users can authenticate using providers like Google, GitHub, Microsoft, and any others you have set up.
---
# DOCUMENT BOUNDARY
---
# Assign roles to users
> Learn how to assign roles to users in your application using to dashboard, SDK, or automated provisioning
After registering roles and permissions for your application, Scalekit provides multiple ways to assign roles to users. These roles allow your app to make the access control decisions as scalekit sends them to your app in the access token.
## Auto assign roles as users join organizations
[Section titled “Auto assign roles as users join organizations”](#auto-assign-roles-as-users-join-organizations)
By default, the organization creator automatically receives the `admin` role, while users who join later receive the `member` role. You can customize these defaults to match your application’s security requirements. For instance, in a CRM system, you may want to set the default role for new members to a read-only role like `viewer` to prevent accidental data modifications.
1. Go to **Dashboard** > **Roles & Permissions** > **Roles** tab
2. Select the roles available and choose defaults for organization creator and member

This automatically assigns these roles to every users who joins any organization in your Scalekit environment.
## Set a default role for new organization members
[Section titled “Set a default role for new organization members”](#set-a-default-role-for-new-organization-members)
You can also configure a default role that is automatically assigned to users who join a specific organization. This organization-level setting **overrides** the application-level default role described above, allowing finer-grained control per organization. 
## Let users assign roles to others API
[Section titled “Let users assign roles to others ”](#let-users-assign-roles-to-others)
Enable organization administrators to manage user roles directly within your application. By building features like “Change role” or “Assign permissions” into your app, you can provide a management experience without requiring administrators to leave your app.
To implement role assignment functionality, follow these essential prerequisites:
1. **Verify administrator permissions**: Ensure the user performing the role assignment has the `admin` role or an equivalent role with the necessary permissions. Check the `permissions` property in their access token to confirm they have role management capabilities.
* Node.js
Verify permissions
```javascript
1
// Decode JWT and check admin permissions
2
const decodedToken = decodeJWT(adminAccessToken);
3
4
// Check if user has admin role or required permissions
5
const isAdmin = decodedToken.roles.includes('admin');
6
const hasPermission = decodedToken.permissions?.includes('users.write') ||
7
decodedToken.permissions?.includes('roles.assign');
8
9
if (!isAdmin && !hasPermission) {
10
throw new Error('Insufficient permissions to assign roles');
11
}
```
* Python
Verify permissions
```python
1
# Decode JWT and check admin permissions
2
decoded_token = decode_jwt(access_token)
3
4
# Check if user has admin role or required permissions
5
is_admin = 'admin' in decoded_token.get('roles', [])
6
has_permission = any(perm in decoded_token.get('permissions', [])
7
for perm in ['users.write', 'roles.assign'])
8
9
if not is_admin and not has_permission:
10
raise PermissionError("Insufficient permissions to assign roles")
```
* Go
Verify permissions
```go
1
// Decode JWT and check admin permissions
2
decodedToken, err := decodeJWT(accessToken)
3
if err != nil {
4
return ValidationResult{Success: false, Error: "Invalid token"}
5
}
6
7
// Check if user has admin role or required permissions
8
roles := decodedToken["roles"].([]interface{})
9
permissions := decodedToken["permissions"].([]interface{})
10
11
isAdmin := false
12
hasPermission := false
13
14
for _, role := range roles {
15
if role == "admin" {
16
isAdmin = true
17
break
18
}
19
}
20
21
for _, perm := range permissions {
22
if perm == "users.write" || perm == "roles.assign" {
23
hasPermission = true
24
break
25
}
26
}
27
28
if !isAdmin && !hasPermission {
29
return ValidationResult{Success: false, Error: "Insufficient permissions"}
30
}
```
* Java
Verify permissions
```java
1
// Decode JWT and check admin permissions
2
Claims decodedToken = decodeJWT(accessToken);
3
4
@SuppressWarnings("unchecked")
5
List userRoles = (List) decodedToken.get("roles");
6
@SuppressWarnings("unchecked")
7
List permissions = (List) decodedToken.get("permissions");
8
9
// Check if user has admin role or required permissions
10
boolean isAdmin = userRoles != null && userRoles.contains("admin");
11
boolean hasPermission = permissions != null &&
12
(permissions.contains("users.write") || permissions.contains("roles.assign"));
13
14
if (!isAdmin && !hasPermission) {
15
throw new SecurityException("Insufficient permissions to assign roles");
16
}
```
2. **Collect required identifiers**: Gather the necessary parameters for the API call:
* `user_id`: The unique identifier of the user whose role you’re changing
* `organization_id`: The organization where the role assignment applies
* `roles`: An array of role names to assign to the user
- Node.js
Collect and validate identifiers
```javascript
1
// Structure and validate role assignment data
2
const roleAssignmentData = {
3
user_id: targetUserId,
4
organization_id: targetOrgId,
5
roles: newRoles,
6
// Additional metadata for auditing
7
performed_by: decodedToken.sub,
8
timestamp: new Date().toISOString()
9
};
10
11
// Validate required fields
12
if (!roleAssignmentData.user_id || !roleAssignmentData.organization_id || !roleAssignmentData.roles) {
13
throw new Error('Missing required identifiers for role assignment');
14
}
```
- Python
Collect and validate identifiers
```python
1
# Structure and validate role assignment data
2
role_assignment_data = {
3
'user_id': target_user_id,
4
'organization_id': target_org_id,
5
'roles': new_roles,
6
# Additional metadata for auditing
7
'performed_by': decoded_token.get('sub'),
8
'timestamp': datetime.utcnow().isoformat()
9
}
10
11
# Validate required fields
12
if not all([role_assignment_data['user_id'],
13
role_assignment_data['organization_id'],
14
role_assignment_data['roles']]):
15
raise ValueError("Missing required identifiers for role assignment")
```
- Go
Collect and validate identifiers
```go
1
// Structure and validate role assignment data
2
roleAssignmentData := map[string]interface{}{
3
"user_id": req.UserID,
4
"organization_id": req.OrganizationID,
5
"roles": req.Roles,
6
// Additional metadata for auditing
7
"performed_by": decodedToken["sub"],
8
"timestamp": time.Now().UTC().Format(time.RFC3339),
9
}
10
11
// Validate required fields
12
if req.UserID == "" || req.OrganizationID == "" || len(req.Roles) == 0 {
13
return ValidationResult{Success: false, Error: "Missing required identifiers"}
14
}
```
- Java
Collect and validate identifiers
```java
1
// Structure and validate role assignment data
2
Map roleAssignmentData = new HashMap<>();
3
roleAssignmentData.put("user_id", request.userId);
4
roleAssignmentData.put("organization_id", request.organizationId);
5
roleAssignmentData.put("roles", request.roles);
6
7
// Additional metadata for auditing
8
roleAssignmentData.put("performed_by", decodedToken.getSubject());
9
roleAssignmentData.put("timestamp", Instant.now().toString());
10
11
// Validate required fields
12
if (request.userId == null || request.organizationId == null || request.roles == null) {
13
throw new IllegalArgumentException("Missing required identifiers for role assignment");
14
}
```
3. **Call Scalekit SDK to update user role**: Use the validated data to make the API call that assigns the new roles to the user through the Scalekit membership update endpoint.
* Node.js
Update user role with Scalekit SDK
```javascript
1
// Use case: Update user membership after validation
2
const validationResult = await prepareRoleAssignment(
3
adminAccessToken,
4
targetUserId,
5
targetOrgId,
6
newRoles
7
);
8
9
if (!validationResult.success) {
10
return res.status(403).json({ error: validationResult.error });
11
}
12
13
// Initialize Scalekit client (reference installation guide for setup)
14
const scalekit = new ScalekitClient(
15
process.env.SCALEKIT_ENVIRONMENT_URL,
16
process.env.SCALEKIT_CLIENT_ID,
17
process.env.SCALEKIT_CLIENT_SECRET
18
);
19
20
// Make the API call to update user roles
21
try {
22
const result = await scalekit.user.updateMembership({
23
user_id: validationResult.data.user_id,
24
organization_id: validationResult.data.organization_id,
25
roles: validationResult.data.roles
26
});
27
28
console.log(`Role assigned successfully:`, result);
29
return res.json({
30
success: true,
31
message: "Role updated successfully",
32
data: result
33
});
34
} catch (error) {
35
console.error(`Failed to assign role: ${error.message}`);
36
return res.status(500).json({
37
error: "Failed to update role",
38
details: error.message
39
});
40
}
```
* Python
Update user role with Scalekit SDK
```python
1
# Use case: Update user membership after validation
2
validation_result = prepare_role_assignment(
3
access_token,
4
target_user_id,
5
target_org_id,
6
new_roles
7
)
8
9
if not validation_result['success']:
10
return jsonify({'error': validation_result['error']}), 403
11
12
# Initialize Scalekit client (reference installation guide for setup)
13
scalekit_client = ScalekitClient(
14
env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
15
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
16
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
17
)
18
19
# Make the API call to update user roles
20
try:
21
from scalekit.v1.users.users_pb2 import UpdateMembershipRequest
22
23
request = UpdateMembershipRequest(
24
user_id=validation_result['data']['user_id'],
25
organization_id=validation_result['data']['organization_id'],
26
roles=validation_result['data']['roles']
27
)
28
29
result = scalekit_client.users.update_membership(request=request)
30
print(f"Role assigned successfully: {result}")
31
32
return jsonify({
33
'success': True,
34
'message': 'Role updated successfully',
35
'data': str(result)
36
})
37
38
except Exception as error:
39
print(f"Failed to assign role: {error}")
40
return jsonify({
41
'error': 'Failed to update role',
42
'details': str(error)
43
}), 500
```
* Go
Update user role with Scalekit SDK
```go
1
// Use case: Update user membership after validation
2
validationResult := prepareRoleAssignment(ctx, accessToken, req)
3
4
if !validationResult.Success {
5
http.Error(w, validationResult.Error, http.StatusForbidden)
6
return
7
}
8
9
// Initialize Scalekit client (reference installation guide for setup)
10
scalekitClient := scalekit.NewScalekitClient(
11
os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
12
os.Getenv("SCALEKIT_CLIENT_ID"),
13
os.Getenv("SCALEKIT_CLIENT_SECRET"),
14
)
15
16
// Make the API call to update user roles
17
data := validationResult.Data.(map[string]interface{})
18
updateRequest := &scalekit.UpdateMembershipRequest{
19
UserId: data["user_id"].(string),
20
OrganizationId: data["organization_id"].(string),
21
Roles: data["roles"].([]string),
22
}
23
24
result, err := scalekitClient.Membership().UpdateMembership(ctx, updateRequest)
25
if err != nil {
26
log.Printf("Failed to assign role: %v", err)
27
http.Error(w, "Failed to update role", http.StatusInternalServerError)
28
return
29
}
30
31
log.Printf("Role assigned successfully: %+v", result)
32
json.NewEncoder(w).Encode(map[string]interface{}{
33
"success": true,
34
"message": "Role updated successfully",
35
"data": result,
36
})
```
* Java
Update user role with Scalekit SDK
```java
1
// Use case: Update user membership after validation
2
ValidationResult validationResult = prepareRoleAssignment(accessToken, request);
3
4
if (!validationResult.success) {
5
return ResponseEntity.status(403).body(Map.of("error", validationResult.error));
6
}
7
8
// Initialize Scalekit client (reference installation guide for setup)
9
ScalekitClient scalekitClient = new ScalekitClient(
10
System.getenv("SCALEKIT_ENVIRONMENT_URL"),
11
System.getenv("SCALEKIT_CLIENT_ID"),
12
System.getenv("SCALEKIT_CLIENT_SECRET")
13
);
14
15
// Make the API call to update user roles
16
try {
17
@SuppressWarnings("unchecked")
18
Map data = (Map) validationResult.data;
19
20
UpdateMembershipRequest updateRequest = UpdateMembershipRequest.newBuilder()
21
.setUserId((String) data.get("user_id"))
22
.setOrganizationId((String) data.get("organization_id"))
23
.addAllRoles((List) data.get("roles"))
24
.build();
25
26
UpdateMembershipResponse response = scalekitClient.users().updateMembership(updateRequest);
27
System.out.println("Role assigned successfully: " + response);
28
29
return ResponseEntity.ok(Map.of(
30
"success", true,
31
"message", "Role updated successfully",
32
"data", response.toString()
33
));
34
35
} catch (Exception e) {
36
System.err.println("Failed to assign role: " + e.getMessage());
37
return ResponseEntity.status(500).body(Map.of(
38
"error", "Failed to update role",
39
"details", e.getMessage()
40
));
41
}
```
4. **Handle response and provide feedback**: Return appropriate success/error responses to the administrator and update your application’s UI accordingly.
* Node.js
Handle API response
```javascript
1
// Success response handling
2
if (result.success) {
3
// Update UI to reflect role change
4
await updateUserInterface(targetUserId, newRoles);
5
6
// Send notification to user (optional)
7
await notifyUserOfRoleChange(targetUserId, newRoles);
8
9
// Log the action for audit purposes
10
await logRoleChange({
11
performed_by: decodedToken.sub,
12
target_user: targetUserId,
13
organization: targetOrgId,
14
old_roles: previousRoles,
15
new_roles: newRoles,
16
timestamp: new Date().toISOString()
17
});
18
}
```
* Python
Handle API response
```python
1
# Success response handling
2
if result.get('success'):
3
# Update UI to reflect role change
4
await update_user_interface(target_user_id, new_roles)
5
6
# Send notification to user (optional)
7
await notify_user_of_role_change(target_user_id, new_roles)
8
9
# Log the action for audit purposes
10
await log_role_change({
11
'performed_by': decoded_token.get('sub'),
12
'target_user': target_user_id,
13
'organization': target_org_id,
14
'old_roles': previous_roles,
15
'new_roles': new_roles,
16
'timestamp': datetime.utcnow().isoformat()
17
})
```
* Go
Handle API response
```go
1
// Success response handling
2
if success {
3
// Update UI to reflect role change
4
updateUserInterface(targetUserID, newRoles)
5
6
// Send notification to user (optional)
7
notifyUserOfRoleChange(targetUserID, newRoles)
8
9
// Log the action for audit purposes
10
logRoleChange(map[string]interface{}{
11
"performed_by": decodedToken["sub"],
12
"target_user": targetUserID,
13
"organization": targetOrgID,
14
"old_roles": previousRoles,
15
"new_roles": newRoles,
16
"timestamp": time.Now().UTC().Format(time.RFC3339),
17
})
18
}
```
* Java
Handle API response
```java
1
// Success response handling
2
if (response.getBody().containsKey("success") &&
3
Boolean.TRUE.equals(response.getBody().get("success"))) {
4
5
// Update UI to reflect role change
6
updateUserInterface(targetUserId, newRoles);
7
8
// Send notification to user (optional)
9
notifyUserOfRoleChange(targetUserId, newRoles);
10
11
// Log the action for audit purposes
12
logRoleChange(Map.of(
13
"performed_by", decodedToken.getSubject(),
14
"target_user", targetUserId,
15
"organization", targetOrgId,
16
"old_roles", previousRoles,
17
"new_roles", newRoles,
18
"timestamp", Instant.now().toString()
19
));
20
}
```
---
# DOCUMENT BOUNDARY
---
# Create and manage roles and permissions
> Set up roles and permissions to control access in your application
Before writing any code, take a moment to plan your application’s authorization model. A well-designed structure for roles and permissions is crucial for security and maintainability. Start by considering the following questions:
* What are the actions your users can perform?
* How many distinct roles does your application need?
Your application’s use cases will determine the answers. Here are a few common patterns:
* **Simple roles**: Some applications, like an online whiteboarding tool, may only need a few roles with implicit permissions. For example, `Admin`, `Editor`, and `Viewer`. In this case, you might not even need to define granular permissions.
* **Pre-defined roles and permissions**: Many applications have a fixed set of roles built from specific permissions. For a project management tool, you could define permissions like `projects:create` and `tasks:assign`, then group them into roles like `Project Manager` and `Team Member`.
* **Customer-defined Roles**: For complex applications, you might allow organization owners to create custom roles with a specific set of permissions. These roles are specific to an organization rather than global to your application.
Scalekit provides the flexibility to build authorization for any of these use cases. Once you have a clear plan, you can start creating your permissions and roles.
Define the permissions your application needs by registering them with Scalekit. Use the `resource:action` format for clear, self-documenting permission names. You can skip this step, in case permissions may not fit your app’s authorization model.
1. ## Define the actions your users can perform as permissions
[Section titled “Define the actions your users can perform as permissions”](#define-the-actions-your-users-can-perform-as-permissions)
* Node.js
Create permissions
```javascript
9 collapsed lines
1
// Initialize Scalekit client
2
// Use case: Register all available actions in your project management app
3
import { ScalekitClient } from "@scalekit-sdk/node";
4
5
const scalekit = new ScalekitClient(
6
process.env.SCALEKIT_ENVIRONMENT_URL,
7
process.env.SCALEKIT_CLIENT_ID,
8
process.env.SCALEKIT_CLIENT_SECRET
9
);
10
11
// Define your application's permissions
12
const permissions = [
13
{
14
name: "projects:create",
15
description: "Allows users to create new projects"
16
},
17
{
18
name: "projects:read",
19
description: "Allows users to view project details"
20
},
21
{
22
name: "projects:update",
23
description: "Allows users to modify existing projects"
24
},
25
{
26
name: "projects:delete",
27
description: "Allows users to remove projects"
28
},
29
{
30
name: "tasks:assign",
31
description: "Allows users to assign tasks to team members"
32
}
33
];
34
35
// Register each permission with Scalekit
36
for (const permission of permissions) {
37
await scalekit.permission.createPermission(permission);
38
console.log(`Created permission: ${permission.name}`);
39
}
40
41
// Your application's permissions are now registered with Scalekit
```
* Python
Create permissions
```python
12 collapsed lines
1
# Initialize Scalekit client
2
# Use case: Register all available actions in your project management app
3
from scalekit import ScalekitClient
4
5
scalekit_client = ScalekitClient(
6
env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
7
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
8
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
9
)
10
11
# Define your application's permissions
12
from scalekit.v1.roles.roles_pb2 import CreatePermission
13
14
permissions = [
15
CreatePermission(
16
name="projects:create",
17
description="Allows users to create new projects"
18
),
19
CreatePermission(
20
name="projects:read",
21
description="Allows users to view project details"
22
),
23
CreatePermission(
24
name="projects:update",
25
description="Allows users to modify existing projects"
26
),
27
CreatePermission(
28
name="projects:delete",
29
description="Allows users to remove projects"
30
),
31
CreatePermission(
32
name="tasks:assign",
33
description="Allows users to assign tasks to team members"
34
)
35
]
36
37
# Register each permission with Scalekit
38
for permission in permissions:
39
scalekit_client.permissions.create_permission(permission=permission)
40
print(f"Created permission: {permission.name}")
41
42
# Your application's permissions are now registered with Scalekit
```
* Go
Create permissions
```go
17 collapsed lines
1
// Initialize Scalekit client
2
// Use case: Register all available actions in your project management app
3
package main
4
5
import (
6
"context"
7
"log"
8
"github.com/scalekit-inc/scalekit-sdk-go"
9
)
10
11
func main() {
12
sc := scalekit.NewScalekitClient(
13
os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
14
os.Getenv("SCALEKIT_CLIENT_ID"),
15
os.Getenv("SCALEKIT_CLIENT_SECRET"),
16
)
17
18
// Define your application's permissions
19
permissions := []*scalekit.CreatePermission{
20
{
21
Name: "projects:create",
22
Description: "Allows users to create new projects",
23
},
24
{
25
Name: "projects:read",
26
Description: "Allows users to view project details",
27
},
28
{
29
Name: "projects:update",
30
Description: "Allows users to modify existing projects",
31
},
32
{
33
Name: "projects:delete",
34
Description: "Allows users to remove projects",
35
},
36
{
37
Name: "tasks:assign",
38
Description: "Allows users to assign tasks to team members",
39
},
40
}
41
42
// Register each permission with Scalekit
43
for _, permission := range permissions {
44
_, err := sc.Permission().CreatePermission(ctx, permission)
45
if err != nil {
46
log.Printf("Failed to create permission: %s", permission.Name)
47
continue
48
}
49
fmt.Printf("Created permission: %s\n", permission.Name)
50
}
51
52
// Your application's permissions are now registered with Scalekit
53
}
```
* Java
Create permissions
```java
11 collapsed lines
1
// Initialize Scalekit client
2
// Use case: Register all available actions in your project management app
3
import com.scalekit.ScalekitClient;
4
import com.scalekit.grpc.scalekit.v1.roles.*;
5
6
ScalekitClient scalekitClient = new ScalekitClient(
7
System.getenv("SCALEKIT_ENVIRONMENT_URL"),
8
System.getenv("SCALEKIT_CLIENT_ID"),
9
System.getenv("SCALEKIT_CLIENT_SECRET")
10
);
11
12
// Define your application's permissions
13
List permissions = Arrays.asList(
14
CreatePermission.newBuilder()
15
.setName("projects:create")
16
.setDescription("Allows users to create new projects")
17
.build(),
18
CreatePermission.newBuilder()
19
.setName("projects:read")
20
.setDescription("Allows users to view project details")
21
.build(),
22
CreatePermission.newBuilder()
23
.setName("projects:update")
24
.setDescription("Allows users to modify existing projects")
25
.build(),
26
CreatePermission.newBuilder()
27
.setName("projects:delete")
28
.setDescription("Allows users to remove projects")
29
.build(),
30
CreatePermission.newBuilder()
31
.setName("tasks:assign")
32
.setDescription("Allows users to assign tasks to team members")
33
.build()
34
);
35
36
// Register each permission with Scalekit
37
for (CreatePermission permission : permissions) {
38
try {
39
CreatePermissionRequest request = CreatePermissionRequest.newBuilder()
40
.setPermission(permission)
41
.build();
42
43
scalekitClient.permissions().createPermission(request);
44
System.out.println("Created permission: " + permission.getName());
45
} catch (Exception e) {
46
System.err.println("Error creating permission: " + e.getMessage());
47
}
48
}
49
50
// Your application's permissions are now registered with Scalekit
```
2. ## Register roles your applications will use
[Section titled “Register roles your applications will use”](#register-roles-your-applications-will-use)
Once you have defined permissions, group them into roles that match your application’s access patterns.
* Node.js
Create roles with permissions
```javascript
1
// Define roles with their associated permissions
2
// Use case: Create standard roles for your project management application
3
const roles = [
4
{
5
name: 'project_admin',
6
display_name: 'Project Administrator',
7
description: 'Full access to manage projects and team members',
8
permissions: [
9
'projects:create', 'projects:read', 'projects:update', 'projects:delete',
10
'tasks:assign'
11
]
12
},
13
{
14
name: 'project_manager',
15
display_name: 'Project Manager',
16
description: 'Can manage projects and assign tasks',
17
permissions: [
18
'projects:create', 'projects:read', 'projects:update',
19
'tasks:assign'
20
]
21
},
22
{
23
name: 'team_member',
24
display_name: 'Team Member',
25
description: 'Can view projects and participate in tasks',
26
permissions: [
27
'projects:read'
28
]
29
}
30
];
31
32
// Register each role with Scalekit
33
for (const role of roles) {
34
await scalekit.role.createRole(role);
35
console.log(`Created role: ${role.name}`);
36
}
37
38
// Your application's roles are now registered with Scalekit
```
* Python
Create roles with permissions
```python
1
# Define roles with their associated permissions
2
# Use case: Create standard roles for your project management application
3
from scalekit.v1.roles.roles_pb2 import CreateRole
4
5
roles = [
6
CreateRole(
7
name="project_admin",
8
display_name="Project Administrator",
9
description="Full access to manage projects and team members",
10
permissions=["projects:create", "projects:read", "projects:update", "projects:delete", "tasks:assign"]
11
),
12
CreateRole(
13
name="project_manager",
14
display_name="Project Manager",
15
description="Can manage projects and assign tasks",
16
permissions=["projects:create", "projects:read", "projects:update", "tasks:assign"]
17
),
18
CreateRole(
19
name="team_member",
20
display_name="Team Member",
21
description="Can view projects and participate in tasks",
22
permissions=["projects:read"]
23
)
24
]
25
26
# Register each role with Scalekit
27
for role in roles:
28
scalekit_client.roles.create_role(role=role)
29
print(f"Created role: {role.name}")
30
31
# Your application's roles are now registered with Scalekit
```
* Go
Create roles with permissions
```go
1
// Define roles with their associated permissions
2
// Use case: Create standard roles for your project management application
3
roles := []*scalekit.CreateRole{
4
{
5
Name: "project_admin",
6
DisplayName: "Project Administrator",
7
Description: "Full access to manage projects and team members",
8
Permissions: []string{"projects:create", "projects:read", "projects:update", "projects:delete", "tasks:assign"},
9
},
10
{
11
Name: "project_manager",
12
DisplayName: "Project Manager",
13
Description: "Can manage projects and assign tasks",
14
Permissions: []string{"projects:create", "projects:read", "projects:update", "tasks:assign"},
15
},
16
{
17
Name: "team_member",
18
DisplayName: "Team Member",
19
Description: "Can view projects and participate in tasks",
20
Permissions: []string{"projects:read"},
21
},
22
}
23
24
// Register each role with Scalekit
25
for _, role := range roles {
26
_, err := sc.Role().CreateRole(ctx, role)
27
if err != nil {
28
log.Printf("Failed to create role: %s", role.Name)
29
continue
30
}
31
fmt.Printf("Created role: %s\n", role.Name)
32
}
33
34
// Your application's roles are now registered with Scalekit
```
* Java
Create roles with permissions
```java
1
// Define roles with their associated permissions
2
// Use case: Create standard roles for your project management application
3
List roles = Arrays.asList(
4
CreateRole.newBuilder()
5
.setName("project_admin")
6
.setDisplayName("Project Administrator")
7
.setDescription("Full access to manage projects and team members")
8
.addAllPermissions(Arrays.asList("projects:create", "projects:read", "projects:update", "projects:delete", "tasks:assign"))
9
.build(),
10
CreateRole.newBuilder()
11
.setName("project_manager")
12
.setDisplayName("Project Manager")
13
.setDescription("Can manage projects and assign tasks")
14
.addAllPermissions(Arrays.asList("projects:create", "projects:read", "projects:update", "tasks:assign"))
15
.build(),
16
CreateRole.newBuilder()
17
.setName("team_member")
18
.setDisplayName("Team Member")
19
.setDescription("Can view projects and participate in tasks")
20
.addPermissions("projects:read")
21
.build()
22
);
23
24
// Register each role with Scalekit
25
for (CreateRole role : roles) {
26
try {
27
CreateRoleRequest request = CreateRoleRequest.newBuilder()
28
.setRole(role)
29
.build();
30
31
scalekitClient.roles().createRole(request);
32
System.out.println("Created role: " + role.getName());
33
} catch (Exception e) {
34
System.err.println("Error creating role: " + e.getMessage());
35
}
36
}
37
38
// Your application's roles are now registered with Scalekit
```
## Inherit permissions through roles
[Section titled “Inherit permissions through roles”](#inherit-permissions-through-roles)
Large applications with extensive feature sets require sophisticated role and permission management. Scalekit enables role inheritance, allowing you to create a hierarchical access control system. Permissions can be grouped into roles, and new roles can be derived from existing base roles, providing a flexible and scalable approach to defining user access.
Role assignment in Scalekit automatically grants a user all permissions defined within that role.
This is how you can implement use it:
1. Your app defines the permissions and assigns to a role. Let’s say `viewer` role.
2. When creating new role called `editor`, you specify that it inherits the permissions from the `viewer` role.
3. When creating new role called `project_owner`, you specify that it inherits the permissions from the `editor` role.
Take a look at our [Roles and Permissions APIs](https://docs.scalekit.com/apis/#tag/roles/get/api/v1/roles).
## Manage roles and permissions in the dashboard
[Section titled “Manage roles and permissions in the dashboard”](#manage-roles-and-permissions-in-the-dashboard)
For most applications, the simplest way to create and manage roles and permissions is through the Scalekit dashboard. This approach works well when you have a fixed set of roles and permissions that don’t need to be modified by users in your application. You can set up your authorization model once during application configuration and manage it through the dashboard going forward.

1. Navigate to **Dashboard** > **Roles & Permissions** > **Permissions** to create permissions:
* Click **Create Permission** and provide:
* **Name** - Machine-friendly identifier (e.g., `projects:create`)
* **Display Name** - Human-readable label (e.g., “Create Projects”)
* **Description** - Clear explanation of what this permission allows
2. Go to **Dashboard** > **Roles & Permissions** > **Roles** to create roles:
* Click **Create Role** and provide:
* **Name** - Machine-friendly identifier (e.g., `project_manager`)
* **Display Name** - Human-readable label (e.g., “Project Manager”)
* **Description** - Clear explanation of the role’s purpose
* **Permissions** - Select the permissions to include in this role
3. Configure default roles for new users who join organizations
4. Organization administrators can create organization-specific roles by going to **Dashboard** > **Organizations** > **Select organization** > **Roles**
Now that you have created roles and permissions in Scalekit, the next step is to assign these roles to users in your application.
### Configure organization specific roles
[Section titled “Configure organization specific roles”](#configure-organization-specific-roles)
Organization-level roles let organization administrators create custom roles that apply only within their specific organization. These roles are separate from any application-level roles you define.

You can create organization-level roles from the Scalekit Dashboard:
* Go to **Organizations → Select an organization → Roles**
* In **Organization roles** section, Click **+ Add role** and provide:
* **Display name**: Human-readable name (e.g., “Manager”)
* **Name (key)**: Machine-friendly identifier (e.g., `manager`)
* **Description**: Clear explanation of what users with this role can do
---
# DOCUMENT BOUNDARY
---
# Implement access control
> Verify permissions and roles in your application code to control user access
After configuring permissions and roles, the next critical step is implementing access control directly within your application code. This is achieved by carefully examining the roles and permissions embedded in the user’s access token to make authorization decisions.
Scalekit conveniently packages these authorization details during the authentication process, providing you with a comprehensive set of data to make precise access control decisions without requiring additional API calls.
Review the authorization flow
This section focuses on implementing access control, which naturally follows user authentication. We recommend completing the authentication [quickstart](/authenticate/fsa/quickstart) before diving into these access control implementation details.
## Start by inspecting the access token
[Section titled “Start by inspecting the access token”](#start-by-inspecting-the-access-token)
When you [exchange the code for a user profile](/authenticate/fsa/complete-login/), Scalekit also adds additional information that help your app determine the access control decisions.
* Auth result
```js
1
{
2
user: {
3
email: "john.doe@example.com",
4
emailVerified: true,
5
givenName: "John",
6
name: "John Doe",
7
id: "usr_74599896446906854"
8
},
9
idToken: "eyJhbGciO..", // Decode for full user details
10
11
accessToken: "eyJhbGciOi..",
12
refreshToken: "rt_8f7d6e5c4b3a2d1e0f9g8h7i6j..",
13
expiresIn: 299 // in seconds
14
}
```
* Decoded ID token
ID token decoded
```json
1
{
2
"at_hash": "ec_jU2ZKpFelCKLTRWiRsg",
3
"aud": [
4
"skc_58327482062864390"
5
],
6
"azp": "skc_58327482062864390",
7
"c_hash": "6wMreK9kWQQY6O5R0CiiYg",
8
"client_id": "skc_58327482062864390",
9
"email": "john.doe@example.com",
10
"email_verified": true,
11
"exp": 1742975822,
12
"family_name": "Doe",
13
"given_name": "John",
14
"iat": 1742974022,
15
"iss": "https://scalekit-z44iroqaaada-dev.scalekit.cloud",
16
"name": "John Doe",
17
"oid": "org_59615193906282635",
18
"sid": "ses_65274187031249433",
19
"sub": "usr_63261014140912135"
20
}
```
* Decoded access token
Decoded access token
```json
1
{
2
"aud": [
3
"prd_skc_7848964512134X699"
4
],
5
"client_id": "prd_skc_7848964512134X699",
6
"exp": 1758265247,
7
"iat": 1758264947,
8
"iss": "https://login.devramp.ai",
9
"jti": "tkn_90928731115292X63",
10
"nbf": 1758264947,
11
"oid": "org_89678001X21929734",
12
"permissions": [
13
"workspace_data:write",
14
"workspace_data:read"
15
],
16
"roles": [
17
"admin"
18
],
19
"sid": "ses_90928729571723X24",
20
"sub": "usr_8967800122X995270",
21
// External identifiers if updated on Scalekit
22
"xoid": "ext_org_123", // Organization ID
23
"xuid": "ext_usr_456", // User ID
24
}
```
Let’s closely look at the access token:
Decoded access token
```json
{
"aud": ["skc_987654321098765432"],
"client_id": "skc_987654321098765432",
"exp": 1750850145,
"iat": 1750849845,
"iss": "http://example.localhost:8889",
"jti": "tkn_987654321098765432",
"nbf": 1750849845,
"roles": ["project_manager", "member"],
"oid": "org_69615647365005430",
"permissions": ["projects:create", "projects:read", "projects:update", "tasks:assign"],
"sid": "ses_987654321098765432",
"sub": "usr_987654321098765432"
}
```
The `roles` and `permissions` values provide runtime insights into the user’s access constraints directly within the access token, eliminating the need for additional API requests. Crucially, always validate the token’s integrity before relying on the embedded authorization details.
* Node.js
Validate and decode access token in middleware
```javascript
1
// Middleware to validate tokens and extract authorization data
2
const validateAndExtractAuth = async (req, res, next) => {
3
try {
4
// Extract access token from cookie (decrypt if needed)
5
const accessToken = decrypt(req.cookies.accessToken);
6
7
// Validate the token using Scalekit SDK
8
const isValid = await scalekit.validateAccessToken(accessToken);
9
10
if (!isValid) {
11
return res.status(401).json({ error: 'Invalid or expired token' });
12
}
13
14
// Decode token to get roles and permissions using any JWT decode library
15
const tokenData = await decodeAccessToken(accessToken);
16
17
// Make authorization data available to route handlers
18
req.user = {
19
id: tokenData.sub,
20
organizationId: tokenData.oid,
21
roles: tokenData.roles || [],
22
permissions: tokenData.permissions || []
23
};
24
25
next();
26
} catch (error) {
27
return res.status(401).json({ error: 'Authentication failed' });
28
}
29
};
```
* Python
Validate and decode access token
```python
4 collapsed lines
1
from scalekit import ScalekitClient
2
from functools import wraps
3
import jwt
4
5
scalekit_client = ScalekitClient(/* your credentials */)
6
7
def validate_and_extract_auth(f):
8
@wraps(f)
9
def decorated_function(*args, **kwargs):
10
try:
11
# Extract access token from cookie (decrypt if needed)
12
access_token = decrypt(request.cookies.get('accessToken'))
13
14
# Validate the token using Scalekit SDK
15
is_valid = scalekit_client.validate_access_token(access_token)
16
17
if not is_valid:
18
return jsonify({'error': 'Invalid or expired token'}), 401
19
20
# Decode token to get roles and permissions
21
token_data = scalekit_client.decode_access_token(access_token)
22
23
# Make authorization data available to route handlers
24
request.user = {
25
'id': token_data.get('sub'),
26
'organization_id': token_data.get('oid'),
27
'roles': token_data.get('roles', []),
28
'permissions': token_data.get('permissions', [])
29
}
30
31
return f(*args, **kwargs)
32
except Exception as e:
33
return jsonify({'error': 'Authentication failed'}), 401
34
35
return decorated_function
```
* Go
Validate and decode access token
```go
7 collapsed lines
1
import (
2
"context"
3
"encoding/json"
4
"net/http"
5
"github.com/scalekit-inc/scalekit-sdk-go"
6
)
7
8
scalekitClient := scalekit.NewScalekitClient(/* your credentials */)
9
10
func validateAndExtractAuth(next http.HandlerFunc) http.HandlerFunc {
11
return func(w http.ResponseWriter, r *http.Request) {
12
// Extract access token from cookie (decrypt if needed)
13
cookie, err := r.Cookie("accessToken")
14
if err != nil {
15
http.Error(w, `{"error": "No access token provided"}`, http.StatusUnauthorized)
16
return
17
}
18
19
accessToken, err := decrypt(cookie.Value)
20
if err != nil {
21
http.Error(w, `{"error": "Token decryption failed"}`, http.StatusUnauthorized)
22
return
23
}
24
25
// Validate the token using Scalekit SDK
26
isValid, err := scalekitClient.ValidateAccessToken(r.Context(), accessToken)
27
if err != nil || !isValid {
28
http.Error(w, `{"error": "Invalid or expired token"}`, http.StatusUnauthorized)
29
return
30
}
31
32
// Decode token to get roles and permissions using any JWT decode lib
33
tokenData, err := DecodeAccessToken(accessToken)
34
if err != nil {
35
http.Error(w, `{"error": "Token decode failed"}`, http.StatusUnauthorized)
36
return
37
}
38
39
// Add authorization data to request context
40
user := map[string]interface{}{
41
"id": tokenData["sub"],
42
"organization_id": tokenData["oid"],
43
"roles": tokenData["roles"],
44
"permissions": tokenData["permissions"],
45
}
46
47
ctx := context.WithValue(r.Context(), "user", user)
48
next(w, r.WithContext(ctx))
49
}
50
}
```
* Java
Validate and decode access token
```java
7 collapsed lines
1
import com.scalekit.ScalekitClient;
2
import javax.servlet.http.HttpServletRequest;
3
import javax.servlet.http.HttpServletResponse;
4
import org.springframework.web.servlet.HandlerInterceptor;
5
import java.util.Map;
6
import java.util.HashMap;
7
8
@Component
9
public class AuthorizationInterceptor implements HandlerInterceptor {
10
private final ScalekitClient scalekit;
11
12
@Override
13
public boolean preHandle(
14
HttpServletRequest request,
15
HttpServletResponse response,
16
Object handler
17
) throws Exception {
18
try {
19
// Extract access token from cookie (decrypt if needed)
20
String accessToken = getCookieValue(request, "accessToken");
21
String decryptedToken = decrypt(accessToken);
22
23
// Validate the token using Scalekit SDK
24
boolean isValid = scalekit.authentication().validateAccessToken(decryptedToken);
25
26
if (!isValid) {
27
response.setStatus(HttpStatus.UNAUTHORIZED.value());
28
response.getWriter().write("{\"error\": \"Invalid or expired token\"}");
29
return false;
30
}
31
32
// Decode token to get roles and permissions using any JWT decode lib
33
Map tokenData = decodeAccessToken(decryptedToken);
34
35
// Make authorization data available to controllers
36
Map user = new HashMap<>();
37
user.put("id", tokenData.get("sub"));
38
user.put("organizationId", tokenData.get("oid"));
39
user.put("roles", tokenData.get("roles"));
40
user.put("permissions", tokenData.get("permissions"));
41
42
request.setAttribute("user", user);
43
return true;
44
45
} catch (Exception e) {
46
response.setStatus(HttpStatus.UNAUTHORIZED.value());
47
response.getWriter().write("{\"error\": \"Authentication failed\"}");
48
return false;
49
}
50
}
51
}
```
This approach makes user roles and permissions available throughout different routes of your application, enabling consistent and secure access control across all endpoints.
## Verify user’s role to allow access to protected resources
[Section titled “Verify user’s role to allow access to protected resources”](#verify-users-role-to-allow-access-to-protected-resources)
Role-based access control (RBAC) provides a straightforward way to manage permissions by grouping them into logical roles. Instead of checking individual permissions for every action, your application can simply verify if the user has the required role, making access control decisions more efficient and easier to maintain.
Tip
Use roles for broad access control patterns like admin access, management privileges, or user tiers. Reserve permissions for fine-grained control over specific actions and resources.
* Node.js
Role-based access control
```javascript
17 collapsed lines
1
// Helper function to check roles
2
function hasRole(user, requiredRole) {
3
return user.roles && user.roles.includes(requiredRole);
4
}
5
6
// Middleware to require specific roles
7
function requireRole(role) {
8
return (req, res, next) => {
9
if (!hasRole(req.user, role)) {
10
return res.status(403).json({
11
error: `Access denied. Required role: ${role}`
12
});
13
}
14
next();
15
};
16
}
17
18
// Admin-only routes
19
app.get('/api/admin/users', validateAndExtractAuth, requireRole('admin'), (req, res) => {
20
// Only admin users can access this endpoint
21
res.json(getAllUsers(req.user.organizationId));
22
});
23
24
// Multiple role check
25
app.post('/api/admin/invite-user', validateAndExtractAuth, (req, res) => {
26
const user = req.user;
27
28
// Allow admins or managers to invite users
29
if (!hasRole(user, 'admin') && !hasRole(user, 'manager')) {
30
return res.status(403).json({ error: 'Only admins and managers can invite users' });
31
}
32
33
const invitation = createUserInvitation(req.body, user.organizationId);
34
res.json(invitation);
35
});
```
* Python
Role-based access control
```python
17 collapsed lines
1
# Helper function to check roles
2
def has_role(user, required_role):
3
roles = user.get('roles', [])
4
return required_role in roles
5
6
# Decorator to require specific roles
7
def require_role(role):
8
def decorator(f):
9
@wraps(f)
10
def decorated_function(*args, **kwargs):
11
user = getattr(request, 'user', {})
12
if not has_role(user, role):
13
return jsonify({'error': f'Access denied. Required role: {role}'}), 403
14
return f(*args, **kwargs)
15
return decorated_function
16
return decorator
17
18
# Admin-only routes
19
@app.route('/api/admin/users')
20
@validate_and_extract_auth
21
@require_role('admin')
22
def get_all_users():
23
# Only admin users can access this endpoint
24
return jsonify(get_all_users_for_org(request.user['organization_id']))
25
26
# Multiple role check
27
@app.route('/api/admin/invite-user', methods=['POST'])
28
@validate_and_extract_auth
29
def invite_user():
30
user = request.user
31
32
# Allow admins or managers to invite users
33
if not has_role(user, 'admin') and not has_role(user, 'manager'):
34
return jsonify({'error': 'Only admins and managers can invite users'}), 403
35
36
invitation = create_user_invitation(request.json, user['organization_id'])
37
return jsonify(invitation)
```
* Go
Role-based access control
```go
31 collapsed lines
1
// Helper function to check roles
2
func hasRole(user map[string]interface{}, requiredRole string) bool {
3
roles, ok := user["roles"].([]interface{})
4
if !ok {
5
return false
6
}
7
8
for _, role := range roles {
9
if roleStr, ok := role.(string); ok && roleStr == requiredRole {
10
return true
11
}
12
}
13
return false
14
}
15
16
// Middleware to require specific roles
17
func requireRole(role string) func(http.HandlerFunc) http.HandlerFunc {
18
return func(next http.HandlerFunc) http.HandlerFunc {
19
return func(w http.ResponseWriter, r *http.Request) {
20
user := r.Context().Value("user").(map[string]interface{})
21
22
if !hasRole(user, role) {
23
http.Error(w, fmt.Sprintf(`{"error": "Access denied. Required role: %s"}`, role), http.StatusForbidden)
24
return
25
}
26
27
next(w, r)
28
}
29
}
30
}
31
32
// Admin-only routes
33
func getAllUsersHandler(w http.ResponseWriter, r *http.Request) {
34
user := r.Context().Value("user").(map[string]interface{})
35
orgId := user["organization_id"].(string)
36
37
// Only admin users can access this endpoint
38
users := getAllUsersForOrg(orgId)
39
json.NewEncoder(w).Encode(users)
40
}
41
42
// Route setup with role middleware
43
http.HandleFunc("/api/admin/users", validateAndExtractAuth(requireRole("admin")(getAllUsersHandler)))
```
* Java
Role-based access control
```java
1
@RestController
2
public class AdminController {
7 collapsed lines
3
4
// Helper method to check roles
5
private boolean hasRole(Map user, String requiredRole) {
6
List roles = (List) user.get("roles");
7
return roles != null && roles.contains(requiredRole);
8
}
9
10
// Admin-only endpoint
11
@GetMapping("/api/admin/users")
12
public ResponseEntity> getAllUsers(HttpServletRequest request) {
13
Map user = (Map) request.getAttribute("user");
14
15
// Check for admin role
16
if (!hasRole(user, "admin")) {
17
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
18
}
19
20
String orgId = (String) user.get("organizationId");
21
List users = userService.getAllUsersForOrg(orgId);
22
return ResponseEntity.ok(users);
23
}
24
25
@PostMapping("/api/admin/invite-user")
26
public ResponseEntity inviteUser(
27
@RequestBody InviteUserRequest request,
28
HttpServletRequest httpRequest
29
) {
30
Map user = (Map) httpRequest.getAttribute("user");
31
32
// Allow admins or managers to invite users
33
if (!hasRole(user, "admin") && !hasRole(user, "manager")) {
34
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
35
}
36
37
String orgId = (String) user.get("organizationId");
38
Invitation invitation = userService.createInvitation(request, orgId);
39
return ResponseEntity.ok(invitation);
40
}
41
}
```
## Verify user’s permissions to allow specific actions
[Section titled “Verify user’s permissions to allow specific actions”](#verify-users-permissions-to-allow-specific-actions)
Permission-based access control provides granular control over specific actions and resources within your application. While roles offer broad access patterns, permissions allow you to define exactly what operations users can perform, enabling precise security controls and the principle of least privilege.
Note
Permissions are typically formatted as `resource:action` (e.g., `projects:create`, `users:read`, `reports:delete`) to provide clear, consistent naming conventions that make your access control logic more readable and maintainable.
* Node.js
Permission-based access control
```javascript
17 collapsed lines
1
// Helper function to check permissions
2
function hasPermission(user, requiredPermission) {
3
return user.permissions && user.permissions.includes(requiredPermission);
4
}
5
6
// Middleware to require specific permissions
7
function requirePermission(permission) {
8
return (req, res, next) => {
9
if (!hasPermission(req.user, permission)) {
10
return res.status(403).json({
11
error: `Access denied. Required permission: ${permission}`
12
});
13
}
14
next();
15
};
16
}
17
18
// Protected routes with permission checks
19
app.get('/api/projects', validateAndExtractAuth, requirePermission('projects:read'), (req, res) => {
20
// User has projects:read permission - allow access
21
res.json(getProjects(req.user.organizationId));
22
});
23
24
app.post('/api/projects', validateAndExtractAuth, requirePermission('projects:create'), (req, res) => {
25
// User has projects:create permission - allow creation
26
const newProject = createProject(req.body, req.user.organizationId);
27
res.json(newProject);
28
});
29
30
// Multiple permission check
31
app.delete('/api/projects/:id', validateAndExtractAuth, (req, res) => {
32
const user = req.user;
33
34
// Check if user has either admin role or specific delete permission
35
if (!hasPermission(user, 'projects:delete') && !user.roles.includes('admin')) {
36
return res.status(403).json({ error: 'Cannot delete projects' });
37
}
38
39
deleteProject(req.params.id, user.organizationId);
40
res.json({ success: true });
41
});
```
* Python
Permission-based access control
```python
17 collapsed lines
1
# Helper function to check permissions
2
def has_permission(user, required_permission):
3
permissions = user.get('permissions', [])
4
return required_permission in permissions
5
6
# Decorator to require specific permissions
7
def require_permission(permission):
8
def decorator(f):
9
@wraps(f)
10
def decorated_function(*args, **kwargs):
11
user = getattr(request, 'user', {})
12
if not has_permission(user, permission):
13
return jsonify({'error': f'Access denied. Required permission: {permission}'}), 403
14
return f(*args, **kwargs)
15
return decorated_function
16
return decorator
17
18
# Protected routes with permission checks
19
@app.route('/api/projects')
20
@validate_and_extract_auth
21
@require_permission('projects:read')
22
def get_projects():
23
# User has projects:read permission - allow access
24
return jsonify(get_projects_for_org(request.user['organization_id']))
25
26
@app.route('/api/projects', methods=['POST'])
27
@validate_and_extract_auth
28
@require_permission('projects:create')
29
def create_project():
30
# User has projects:create permission - allow creation
31
new_project = create_project_for_org(request.json, request.user['organization_id'])
32
return jsonify(new_project)
33
34
# Multiple permission check
35
@app.route('/api/projects/', methods=['DELETE'])
36
@validate_and_extract_auth
37
def delete_project(project_id):
38
user = request.user
39
40
# Check if user has either admin role or specific delete permission
41
if not has_permission(user, 'projects:delete') and 'admin' not in user.get('roles', []):
42
return jsonify({'error': 'Cannot delete projects'}), 403
43
44
delete_project_from_org(project_id, user['organization_id'])
45
return jsonify({'success': True})
```
* Go
Permission-based access control
```go
1
// Helper function to check permissions
2
func hasPermission(user map[string]interface{}, requiredPermission string) bool {
3
permissions, ok := user["permissions"].([]interface{})
4
if !ok {
5
return false
6
}
7
8
for _, perm := range permissions {
9
if permStr, ok := perm.(string); ok && permStr == requiredPermission {
10
return true
11
}
12
}
13
return false
14
}
15
16
// Middleware to require specific permissions
17
func requirePermission(permission string) func(http.HandlerFunc) http.HandlerFunc {
18
return func(next http.HandlerFunc) http.HandlerFunc {
19
return func(w http.ResponseWriter, r *http.Request) {
20
user := r.Context().Value("user").(map[string]interface{})
21
22
if !hasPermission(user, permission) {
23
http.Error(w, fmt.Sprintf(`{"error": "Access denied. Required permission: %s"}`, permission), http.StatusForbidden)
24
return
25
}
26
27
next(w, r)
28
}
29
}
30
}
31
32
// Protected routes with permission checks
33
func getProjectsHandler(w http.ResponseWriter, r *http.Request) {
34
user := r.Context().Value("user").(map[string]interface{})
35
orgId := user["organization_id"].(string)
36
37
// User has projects:read permission - allow access
38
projects := getProjectsForOrg(orgId)
39
json.NewEncoder(w).Encode(projects)
40
}
41
42
func createProjectHandler(w http.ResponseWriter, r *http.Request) {
43
user := r.Context().Value("user").(map[string]interface{})
44
orgId := user["organization_id"].(string)
45
46
// User has projects:create permission - allow creation
47
var projectData map[string]interface{}
48
json.NewDecoder(r.Body).Decode(&projectData)
49
50
newProject := createProjectForOrg(projectData, orgId)
51
json.NewEncoder(w).Encode(newProject)
52
}
53
54
// Route setup with middleware
55
http.HandleFunc("/api/projects", validateAndExtractAuth(requirePermission("projects:read")(getProjectsHandler)))
56
http.HandleFunc("/api/projects/create", validateAndExtractAuth(requirePermission("projects:create")(createProjectHandler)))
```
* Java
Permission-based access control
```java
1
@RestController
2
public class ProjectController {
3
4
// Helper method to check permissions
5
private boolean hasPermission(Map user, String requiredPermission) {
6
List permissions = (List) user.get("permissions");
7
return permissions != null && permissions.contains(requiredPermission);
8
}
9
10
// Annotation-based permission checking
11
@GetMapping("/api/projects")
12
@PreAuthorize("hasPermission('projects:read')")
13
public ResponseEntity> getProjects(HttpServletRequest request) {
14
Map user = (Map) request.getAttribute("user");
15
String orgId = (String) user.get("organizationId");
16
17
// User has projects:read permission - allow access
18
List projects = projectService.getProjectsForOrg(orgId);
19
return ResponseEntity.ok(projects);
20
}
21
22
@PostMapping("/api/projects")
23
public ResponseEntity createProject(
24
@RequestBody CreateProjectRequest request,
25
HttpServletRequest httpRequest
26
) {
27
Map user = (Map) httpRequest.getAttribute("user");
28
29
// Check permission manually
30
if (!hasPermission(user, "projects:create")) {
31
return ResponseEntity.status(HttpStatus.FORBIDDEN)
32
.body(null);
33
}
34
35
String orgId = (String) user.get("organizationId");
36
Project newProject = projectService.createProject(request, orgId);
37
return ResponseEntity.ok(newProject);
38
}
39
40
@DeleteMapping("/api/projects/{projectId}")
41
public ResponseEntity