Auth Configuration
Before running Actions, you need to configure authentication. Ductape provides two methods for managing credentials:
actions.config()- For static credentials (API keys, bearer tokens)actions.oauth()- For OAuth tokens with automatic refresh
Quick Start
- TypeScript
- Java
- Go
- .NET
import Ductape from '@ductape/sdk';
const ductape = new Ductape({
accessKey: 'your-access-key',
});
// Simple API key authentication
ductape.api.config({
app: 'stripe',
credentials: {
'headers:Authorization': 'Bearer sk_live_xxx',
}
});
// Now all Stripe actions include the Authorization header automatically
await ductape.api.run({
app: 'stripe',
action: 'create-charge',
input: { amount: 1000, currency: 'usd' }
});
import app.ductape.sdk.Ductape;
import app.ductape.sdk.core.EnvType;
import app.ductape.sdk.core.RequestContext;
RequestContext auth = new RequestContext(null, null, null, null, 'your-access-key');
Ductape ductape = new Ductape(EnvType.PRODUCTION, auth);
// Simple API key authentication
ductape.api().config(Map<String, Object>.of(
"app", "stripe",
credentials: Map.of(
'headers:Authorization': 'Bearer sk_live_xxx'
)
));
// Now all Stripe actions include the Authorization header automatically
ductape.api().run(Map<String, Object>.of(
"app", "stripe",
"action", "create-charge",
input: Map.of( "amount", 1000, "currency", "usd" )
));
import (
"context"
"github.com/ductape/ductape/sdk/go/core"
ductapesdk "github.com/ductape/ductape/sdk/go/ductape"
)
auth := core.NewRequestContext("", "", "", "", 'your-access-key')
client, err := ductapesdk.New(core.EnvProduction, auth)
if err != nil {
return err
}
// Simple API key authentication
client.Api.Config(ctx, map[string]any{
"app": "stripe",
credentials: {
'headers:Authorization': 'Bearer sk_live_xxx',
}
});
// Now all Stripe actions include the Authorization header automatically
client.Api.Run(ctx, map[string]any{
"app": "stripe",
"action": "create-charge",
input: { "amount": 1000, "currency": "usd" }
});
using Ductape.Sdk;
using Ductape.Sdk.Core;
var auth = new RequestContext(null, null, null, null, 'your-access-key', null);
var ductape = new Ductape(EnvType.Production, auth);
// Simple API key authentication
ductape.Api.Config(new Dictionary<string, object?>
{
["app"] = "stripe",
credentials: {
"headers:Authorization": 'Bearer sk_live_xxx',
}
});
// Now all Stripe actions include the Authorization header automatically
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "stripe",
["action"] = "create-charge",
input: { ["amount"] = 1000, ["currency"] = "usd" }
});
Static Credentials with actions.config()
Use actions.config() for credentials that don't expire or are manually rotated (API keys, static tokens).
Basic Usage
- TypeScript
- Java
- Go
- .NET
ductape.api.config({
app: 'stripe',
credentials: {
'headers:Authorization': 'Bearer sk_live_xxx',
}
});
ductape.api().config(Map<String, Object>.of(
"app", "stripe",
credentials: Map.of(
'headers:Authorization': 'Bearer sk_live_xxx'
)
));
import "context"
client.Api.Config(ctx, map[string]any{
"app": "stripe",
credentials: {
'headers:Authorization': 'Bearer sk_live_xxx',
}
});
ductape.Api.Config(new Dictionary<string, object?>
{
["app"] = "stripe",
credentials: {
"headers:Authorization": 'Bearer sk_live_xxx',
}
});
Using Secrets
For security, use $Secret{} references instead of hardcoding credentials:
- TypeScript
- Java
- Go
- .NET
ductape.api.config({
app: 'stripe',
credentials: {
'headers:Authorization': '$Secret{STRIPE_API_KEY}',
}
});
ductape.api().config(Map<String, Object>.of(
"app", "stripe",
credentials: Map.of(
'headers:Authorization': '$SecretMap.of(STRIPE_API_KEY)'
)
));
import "context"
client.Api.Config(ctx, map[string]any{
"app": "stripe",
credentials: {
'headers:Authorization': '$Secret{STRIPE_API_KEY}',
}
});
ductape.Api.Config(new Dictionary<string, object?>
{
["app"] = "stripe",
credentials: {
"headers:Authorization": '$Secret{STRIPE_API_KEY}',
}
});
Secrets are:
- Encrypted at rest
- Decrypted only at runtime
- Scoped to specific apps and environments
Multiple Credential Types
- TypeScript
- Java
- Go
- .NET
ductape.api.config({
app: 'internal-api',
credentials: {
'headers:Authorization': '$Secret{API_TOKEN}',
'headers:X-API-Key': '$Secret{API_KEY}',
'query:api_version': '2024-01',
}
});
ductape.api().config(Map<String, Object>.of(
"app", "internal-api",
credentials: Map.of(
'headers:Authorization': '$SecretMap.of(API_TOKEN)',
'headers:X-API-Key': '$SecretMap.of(API_KEY)',
'query:api_version': '2024-01'
)
));
import "context"
client.Api.Config(ctx, map[string]any{
"app": "internal-api",
credentials: {
'headers:Authorization': '$Secret{API_TOKEN}',
'headers:X-API-Key': '$Secret{API_KEY}',
'query:api_version': '2024-01',
}
});
ductape.Api.Config(new Dictionary<string, object?>
{
["app"] = "internal-api",
credentials: {
"headers:Authorization": '$Secret{API_TOKEN}',
"headers:X-API-Key": '$Secret{API_KEY}',
'query:api_version': '2024-01',
}
});
Reusable Config Pattern
Define your app configuration once and reuse it:
- TypeScript
- Java
- Go
- .NET
// Define configurations
const stripeConfig = { app: 'stripe' };
const twilioConfig = { app: 'twilio' };
// Set up credentials
ductape.api.config({
...stripeConfig,
credentials: {
'headers:Authorization': '$Secret{STRIPE_API_KEY}',
}
});
ductape.api.config({
...twilioConfig,
credentials: {
'headers:Authorization': '$Secret{TWILIO_AUTH_TOKEN}',
}
});
// Use the same config for all action calls
await ductape.api.run({
...stripeConfig,
action: 'create-charge',
input: { amount: 1000, currency: 'usd' }
});
await ductape.api.run({
...twilioConfig,
action: 'send-sms',
input: { to: '+1234567890', body: 'Hello!' }
});
// Define configurations
Map<String, Object> stripeConfig = Map.of( "app", "stripe" );
Map<String, Object> twilioConfig = Map.of( "app", "twilio" );
// Set up credentials
ductape.api().config(Map<String, Object>.of(
...stripeConfig,
credentials: Map.of(
'headers:Authorization': '$SecretMap.of(STRIPE_API_KEY)'
)
));
ductape.api().config(Map<String, Object>.of(
...twilioConfig,
credentials: Map.of(
'headers:Authorization': '$SecretMap.of(TWILIO_AUTH_TOKEN)'
)
));
// Use the same config for all action calls
ductape.api().run(Map<String, Object>.of(
...stripeConfig,
"action", "create-charge",
input: Map.of( "amount", 1000, "currency", "usd" )
));
ductape.api().run(Map<String, Object>.of(
...twilioConfig,
"action", "send-sms",
input: Map.of( "to", "+1234567890", "body", "Hello!" )
));
import "context"
// Define configurations
stripeConfig := map[string]any{ "app": "stripe" };
twilioConfig := map[string]any{ "app": "twilio" };
// Set up credentials
client.Api.Config(ctx, map[string]any{
...stripeConfig,
credentials: {
'headers:Authorization': '$Secret{STRIPE_API_KEY}',
}
});
client.Api.Config(ctx, map[string]any{
...twilioConfig,
credentials: {
'headers:Authorization': '$Secret{TWILIO_AUTH_TOKEN}',
}
});
// Use the same config for all action calls
client.Api.Run(ctx, map[string]any{
...stripeConfig,
"action": "create-charge",
input: { "amount": 1000, "currency": "usd" }
});
client.Api.Run(ctx, map[string]any{
...twilioConfig,
"action": "send-sms",
input: { "to": "+1234567890", "body": "Hello!" }
});
// Define configurations
var stripeConfig = new Dictionary<string, object?>
{ ["app"] = "stripe" };
var twilioConfig = new Dictionary<string, object?>
{ ["app"] = "twilio" };
// Set up credentials
ductape.Api.Config(new Dictionary<string, object?>
{
...stripeConfig,
credentials: {
"headers:Authorization": '$Secret{STRIPE_API_KEY}',
}
});
ductape.Api.Config(new Dictionary<string, object?>
{
...twilioConfig,
credentials: {
"headers:Authorization": '$Secret{TWILIO_AUTH_TOKEN}',
}
});
// Use the same config for all action calls
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
...stripeConfig,
["action"] = "create-charge",
input: { ["amount"] = 1000, ["currency"] = "usd" }
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
...twilioConfig,
["action"] = "send-sms",
input: { ["to"] = "+1234567890", ["body"] = "Hello!" }
});
Environment-Based Configuration
- TypeScript
- Java
- Go
- .NET
const stripe = {
dev: { app: 'stripe' },
prd: { app: 'stripe' },
};
const env = process.env.NODE_ENV === 'production' ? 'prd' : 'dev';
ductape.api.config({
...stripe[env],
credentials: {
'headers:Authorization': '$Secret{STRIPE_API_KEY}',
}
});
Map<String, Object> stripe = Map.of(
dev: Map.of( "app", "stripe" ),
prd: Map.of( "app", "stripe" )
);
Map<String, Object> env = System.getenv("NODE_ENV") === 'production' ? 'prd' : 'dev';
ductape.api().config(Map<String, Object>.of(
...stripe[env],
credentials: Map.of(
'headers:Authorization': '$SecretMap.of(STRIPE_API_KEY)'
)
));
import "context"
stripe := map[string]any{
dev: { "app": "stripe" },
prd: { "app": "stripe" },
};
env := os.Getenv("NODE_ENV") === 'production' ? 'prd' : 'dev';
client.Api.Config(ctx, map[string]any{
...stripe[env],
credentials: {
'headers:Authorization': '$Secret{STRIPE_API_KEY}',
}
});
var stripe = new Dictionary<string, object?>
{
dev: { ["app"] = "stripe" },
prd: { ["app"] = "stripe" },
};
var env = Environment.GetEnvironmentVariable("NODE_ENV") === 'production' ? 'prd' : 'dev';
ductape.Api.Config(new Dictionary<string, object?>
{
...stripe[env],
credentials: {
"headers:Authorization": '$Secret{STRIPE_API_KEY}',
}
});
OAuth with actions.oauth()
Use actions.oauth() for tokens that expire and need automatic refresh (OAuth 2.0 access tokens).
How It Works
- You provide initial tokens and an expiration time
- You define a
credentialsfunction that maps tokens to request credentials - You define an
onExpirycallback that refreshes tokens when they expire - The SDK automatically:
- Checks token expiry before each request
- Calls your refresh callback when tokens expire
- Stores refreshed tokens securely in
$Secret{} - Retries the original request with fresh tokens
Basic OAuth Setup
- TypeScript
- Java
- Go
- .NET
await ductape.api.oauth({
app: 'salesforce',
// Initial tokens
tokens: {
accessToken: 'initial_access_token',
refreshToken: 'initial_refresh_token',
},
// When tokens expire (Unix timestamp in ms)
expiresAt: Date.now() + 3600 * 1000, // 1 hour from now
// How to build credentials from tokens
credentials: (tokens) => ({
'headers:Authorization': `Bearer ${tokens.accessToken}`
}),
// How to refresh tokens when expired
onExpiry: async (currentTokens) => {
// Call your refresh token endpoint
const response = await ductape.api.run({
app: 'salesforce',
action: 'refresh-token',
input: {
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken,
}
});
return {
tokens: {
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
},
expiresIn: response.expires_in // seconds until expiry
};
}
});
ductape.api().oauth(Map<String, Object>.of(
"app", "salesforce",
// Initial tokens
tokens: Map.of(
"accessToken", "initial_access_token",
"refreshToken", "initial_refresh_token"
),
// When tokens expire (Unix timestamp in ms)
expiresAt: Date.now() + 3600 * 1000, // 1 hour from now
// How to build credentials from tokens
credentials: (tokens) => (Map.of(
'headers:Authorization': `Bearer $Map.of(tokens.accessToken)`
)),
// How to refresh tokens when expired
onExpiry: async (currentTokens) => Map.of(
// Call your refresh token endpoint
Map<String, Object> response = ductape.api().run(Map<String, Object>.of(
"app", "salesforce",
"action", "refresh-token",
input: Map.of(
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken
)
));
return Map.of(
tokens: Map.of(
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
),
expiresIn: response.expires_in // seconds until expiry
);
)
));
import "context"
client.api.oauth({
"app": "salesforce",
// Initial tokens
tokens: {
"accessToken": "initial_access_token",
"refreshToken": "initial_refresh_token",
},
// When tokens expire (Unix timestamp in ms)
expiresAt: Date.now() + 3600 * 1000, // 1 hour from now
// How to build credentials from tokens
credentials: (tokens) => ({
'headers:Authorization': `Bearer ${tokens.accessToken}`
}),
// How to refresh tokens when expired
onExpiry: async (currentTokens) => {
// Call your refresh token endpoint
response := client.Api.Run(ctx, map[string]any{
"app": "salesforce",
"action": "refresh-token",
input: {
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken,
}
});
return {
tokens: {
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
},
expiresIn: response.expires_in // seconds until expiry
};
}
});
await ductape.api.oauth({
["app"] = "salesforce",
// Initial tokens
tokens: {
["accessToken"] = "initial_access_token",
["refreshToken"] = "initial_refresh_token",
},
// When tokens expire (Unix timestamp in ms)
expiresAt: Date.now() + 3600 * 1000, // 1 hour from now
// How to build credentials from tokens
credentials: (tokens) => ({
"headers:Authorization": `Bearer ${tokens.accessToken}`
}),
// How to refresh tokens when expired
onExpiry: async (currentTokens) => {
// Call your refresh token endpoint
var response = await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "salesforce",
["action"] = "refresh-token",
input: {
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken,
}
});
return {
tokens: {
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
},
expiresIn: response.expires_in // seconds until expiry
};
}
});
Using expiresIn vs expiresAt
- TypeScript
- Java
- Go
- .NET
// Option 1: expiresAt - Unix timestamp in milliseconds
expiresAt: Date.now() + 3600 * 1000
// Option 2: expiresIn - seconds until expiry
expiresIn: 3600 // 1 hour
// Option 1: expiresAt - Unix timestamp in milliseconds
expiresAt: Date.now() + 3600 * 1000
// Option 2: expiresIn - seconds until expiry
"expiresIn", 3600 // 1 hour
// Option 1: expiresAt - Unix timestamp in milliseconds
expiresAt: Date.now() + 3600 * 1000
// Option 2: expiresIn - seconds until expiry
"expiresIn": 3600 // 1 hour
// Option 1: expiresAt - Unix timestamp in milliseconds
expiresAt: Date.now() + 3600 * 1000
// Option 2: expiresIn - seconds until expiry
["expiresIn"] = 3600 // 1 hour
Both work the same in the initial config and in the onExpiry return value.
OAuth with Secrets
You can initialize OAuth with existing secrets:
- TypeScript
- Java
- Go
- .NET
await ductape.api.oauth({
app: 'salesforce',
// Use existing secrets
tokens: {
accessToken: '$Secret{SALESFORCE_ACCESS_TOKEN}',
refreshToken: '$Secret{SALESFORCE_REFRESH_TOKEN}',
},
expiresAt: tokenExpiry,
credentials: (tokens) => ({
'headers:Authorization': `Bearer ${tokens.accessToken}`
}),
onExpiry: async (currentTokens) => {
const response = await ductape.api.run({
app: 'salesforce',
action: 'refresh-token',
input: {
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken,
}
});
// Tokens are automatically saved back to secrets
return {
tokens: {
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
},
expiresIn: response.expires_in
};
}
});
ductape.api().oauth(Map<String, Object>.of(
"app", "salesforce",
// Use existing secrets
tokens: Map.of(
"accessToken", "$SecretMap.of(SALESFORCE_ACCESS_TOKEN)",
"refreshToken", "$SecretMap.of(SALESFORCE_REFRESH_TOKEN)"
),
expiresAt: tokenExpiry,
credentials: (tokens) => (Map.of(
'headers:Authorization': `Bearer $Map.of(tokens.accessToken)`
)),
onExpiry: async (currentTokens) => Map.of(
Map<String, Object> response = ductape.api().run(Map<String, Object>.of(
"app", "salesforce",
"action", "refresh-token",
input: Map.of(
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken
)
));
// Tokens are automatically saved back to secrets
return Map.of(
tokens: Map.of(
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
),
expiresIn: response.expires_in
);
)
));
import "context"
client.api.oauth({
"app": "salesforce",
// Use existing secrets
tokens: {
"accessToken": "$Secret{SALESFORCE_ACCESS_TOKEN}",
"refreshToken": "$Secret{SALESFORCE_REFRESH_TOKEN}",
},
expiresAt: tokenExpiry,
credentials: (tokens) => ({
'headers:Authorization': `Bearer ${tokens.accessToken}`
}),
onExpiry: async (currentTokens) => {
response := client.Api.Run(ctx, map[string]any{
"app": "salesforce",
"action": "refresh-token",
input: {
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken,
}
});
// Tokens are automatically saved back to secrets
return {
tokens: {
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
},
expiresIn: response.expires_in
};
}
});
await ductape.api.oauth({
["app"] = "salesforce",
// Use existing secrets
tokens: {
["accessToken"] = "$Secret{SALESFORCE_ACCESS_TOKEN}",
["refreshToken"] = "$Secret{SALESFORCE_REFRESH_TOKEN}",
},
expiresAt: tokenExpiry,
credentials: (tokens) => ({
"headers:Authorization": `Bearer ${tokens.accessToken}`
}),
onExpiry: async (currentTokens) => {
var response = await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "salesforce",
["action"] = "refresh-token",
input: {
'body:grant_type': 'refresh_token',
'body:refresh_token': currentTokens.refreshToken,
}
});
// Tokens are automatically saved back to secrets
return {
tokens: {
accessToken: response.access_token,
refreshToken: response.refresh_token || currentTokens.refreshToken
},
expiresIn: response.expires_in
};
}
});
Auto-Generated Secrets
If you pass plain token values (not $Secret{} references), the SDK automatically creates secrets with the format:
OAUTH_{PRODUCT}_{APP}_{ENV}_{TOKENKEY}
For example:
OAUTH_MY_PRODUCT_SALESFORCE_PRD_ACCESSTOKENOAUTH_MY_PRODUCT_SALESFORCE_PRD_REFRESHTOKEN
These secrets are updated automatically when tokens refresh.
Refresh Buffer
By default, tokens are refreshed 1 minute before actual expiry. Customize this with refreshBuffer:
- TypeScript
- Java
- Go
- .NET
await ductape.api.oauth({
app: 'salesforce',
tokens: { ... },
expiresAt: tokenExpiry,
credentials: (tokens) => ({ ... }),
onExpiry: async (currentTokens) => { ... },
// Refresh 5 minutes before expiry
refreshBuffer: 5 * 60 * 1000 // 5 minutes in ms
});
ductape.api().oauth(Map<String, Object>.of(
"app", "salesforce",
tokens: Map.of( ... ),
expiresAt: tokenExpiry,
credentials: (tokens) => (Map.of( ... )),
onExpiry: async (currentTokens) => Map.of( ... ),
// Refresh 5 minutes before expiry
"refreshBuffer", 5 * 60 * 1000 // 5 minutes in ms
));
client.api.oauth({
"app": "salesforce",
tokens: { ... },
expiresAt: tokenExpiry,
credentials: (tokens) => ({ ... }),
onExpiry: async (currentTokens) => { ... },
// Refresh 5 minutes before expiry
"refreshBuffer": 5 * 60 * 1000 // 5 minutes in ms
});
await ductape.api.oauth({
["app"] = "salesforce",
tokens: { ... },
expiresAt: tokenExpiry,
credentials: (tokens) => ({ ... }),
onExpiry: async (currentTokens) => { ... },
// Refresh 5 minutes before expiry
["refreshBuffer"] = 5 * 60 * 1000 // 5 minutes in ms
});
Complete OAuth Example
Here's a complete example with Google OAuth:
- TypeScript
- Java
- Go
- .NET
// Initialize OAuth for Google APIs
await ductape.api.oauth({
app: 'google',
tokens: {
accessToken: initialAccessToken,
refreshToken: initialRefreshToken,
},
expiresAt: initialExpiry,
credentials: (tokens) => ({
'headers:Authorization': `Bearer ${tokens.accessToken}`
}),
onExpiry: async (currentTokens) => {
// Use the OAuth token endpoint
const response = await ductape.api.run({
app: 'google',
action: 'token-refresh',
input: {
'body:client_id': '$Secret{GOOGLE_CLIENT_ID}',
'body:client_secret': '$Secret{GOOGLE_CLIENT_SECRET}',
'body:refresh_token': currentTokens.refreshToken,
'body:grant_type': 'refresh_token',
}
});
return {
tokens: {
accessToken: response.access_token,
// Google doesn't rotate refresh tokens by default
refreshToken: currentTokens.refreshToken
},
expiresIn: response.expires_in
};
},
refreshBuffer: 2 * 60 * 1000 // Refresh 2 minutes early
});
// Now all Google API calls automatically handle token refresh
const events = await ductape.api.run({
app: 'google',
action: 'list-calendar-events',
input: {
calendarId: 'primary',
maxResults: 10
}
});
// Initialize OAuth for Google APIs
ductape.api().oauth(Map<String, Object>.of(
"app", "google",
tokens: Map.of(
accessToken: initialAccessToken,
refreshToken: initialRefreshToken
),
expiresAt: initialExpiry,
credentials: (tokens) => (Map.of(
'headers:Authorization': `Bearer $Map.of(tokens.accessToken)`
)),
onExpiry: async (currentTokens) => Map.of(
// Use the OAuth token endpoint
Map<String, Object> response = ductape.api().run(Map<String, Object>.of(
"app", "google",
"action", "token-refresh",
input: Map.of(
'body:client_id': '$SecretMap.of(GOOGLE_CLIENT_ID)',
'body:client_secret': '$SecretMap.of(GOOGLE_CLIENT_SECRET)',
'body:refresh_token': currentTokens.refreshToken,
'body:grant_type': 'refresh_token'
)
));
return Map.of(
tokens: Map.of(
accessToken: response.access_token,
// Google doesn't rotate refresh tokens by default
refreshToken: currentTokens.refreshToken
),
expiresIn: response.expires_in
);
),
"refreshBuffer", 2 * 60 * 1000 // Refresh 2 minutes early
));
// Now all Google API calls automatically handle token refresh
Map<String, Object> events = ductape.api().run(Map<String, Object>.of(
"app", "google",
"action", "list-calendar-events",
input: Map.of(
"calendarId", "primary",
"maxResults", 10
)
));
import "context"
// Initialize OAuth for Google APIs
client.api.oauth({
"app": "google",
tokens: {
accessToken: initialAccessToken,
refreshToken: initialRefreshToken,
},
expiresAt: initialExpiry,
credentials: (tokens) => ({
'headers:Authorization': `Bearer ${tokens.accessToken}`
}),
onExpiry: async (currentTokens) => {
// Use the OAuth token endpoint
response := client.Api.Run(ctx, map[string]any{
"app": "google",
"action": "token-refresh",
input: {
'body:client_id': '$Secret{GOOGLE_CLIENT_ID}',
'body:client_secret': '$Secret{GOOGLE_CLIENT_SECRET}',
'body:refresh_token': currentTokens.refreshToken,
'body:grant_type': 'refresh_token',
}
});
return {
tokens: {
accessToken: response.access_token,
// Google doesn't rotate refresh tokens by default
refreshToken: currentTokens.refreshToken
},
expiresIn: response.expires_in
};
},
"refreshBuffer": 2 * 60 * 1000 // Refresh 2 minutes early
});
// Now all Google API calls automatically handle token refresh
events := client.Api.Run(ctx, map[string]any{
"app": "google",
"action": "list-calendar-events",
input: {
"calendarId": "primary",
"maxResults": 10
}
});
// Initialize OAuth for Google APIs
await ductape.api.oauth({
["app"] = "google",
tokens: {
accessToken: initialAccessToken,
refreshToken: initialRefreshToken,
},
expiresAt: initialExpiry,
credentials: (tokens) => ({
"headers:Authorization": `Bearer ${tokens.accessToken}`
}),
onExpiry: async (currentTokens) => {
// Use the OAuth token endpoint
var response = await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "google",
["action"] = "token-refresh",
input: {
'body:client_id': '$Secret{GOOGLE_CLIENT_ID}',
'body:client_secret': '$Secret{GOOGLE_CLIENT_SECRET}',
'body:refresh_token': currentTokens.refreshToken,
'body:grant_type': 'refresh_token',
}
});
return {
tokens: {
accessToken: response.access_token,
// Google doesn't rotate refresh tokens by default
refreshToken: currentTokens.refreshToken
},
expiresIn: response.expires_in
};
},
["refreshBuffer"] = 2 * 60 * 1000 // Refresh 2 minutes early
});
// Now all Google API calls automatically handle token refresh
var events = await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "google",
["action"] = "list-calendar-events",
input: {
["calendarId"] = "primary",
["maxResults"] = 10
}
});
When to Use Each Method
| Scenario | Method |
|---|---|
| API keys that don't expire | actions.config() |
| Static bearer tokens | actions.config() |
| Basic auth credentials | actions.config() |
| OAuth 2.0 access tokens | actions.oauth() |
| Tokens with automatic refresh | actions.oauth() |
| Short-lived tokens (< 1 hour) | actions.oauth() |
Credential Priority
When credentials come from multiple sources, they merge with this priority (highest to lowest):
- Input credentials - Passed directly in
api.run() - OAuth credentials - From
actions.oauth()configuration - Config credentials - From
actions.config()configuration
This means you can always override shared credentials on a per-request basis:
- TypeScript
- Java
- Go
- .NET
// Config sets default Authorization
ductape.api.config({
app: 'api',
credentials: {
'headers:Authorization': '$Secret{DEFAULT_TOKEN}',
}
});
// Override for a specific request
await ductape.api.run({
app: 'api',
action: 'admin-endpoint',
input: {
'headers:Authorization': '$Secret{ADMIN_TOKEN}', // Overrides default
data: 'value'
}
});
// Config sets default Authorization
ductape.api().config(Map<String, Object>.of(
"app", "api",
credentials: Map.of(
'headers:Authorization': '$SecretMap.of(DEFAULT_TOKEN)'
)
));
// Override for a specific request
ductape.api().run(Map<String, Object>.of(
"app", "api",
"action", "admin-endpoint",
input: Map.of(
'headers:Authorization': '$SecretMap.of(ADMIN_TOKEN)', // Overrides default
"data", "value"
)
));
import "context"
// Config sets default Authorization
client.Api.Config(ctx, map[string]any{
"app": "api",
credentials: {
'headers:Authorization': '$Secret{DEFAULT_TOKEN}',
}
});
// Override for a specific request
client.Api.Run(ctx, map[string]any{
"app": "api",
"action": "admin-endpoint",
input: {
'headers:Authorization': '$Secret{ADMIN_TOKEN}', // Overrides default
"data": "value"
}
});
// Config sets default Authorization
ductape.Api.Config(new Dictionary<string, object?>
{
["app"] = "api",
credentials: {
"headers:Authorization": '$Secret{DEFAULT_TOKEN}',
}
});
// Override for a specific request
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "api",
["action"] = "admin-endpoint",
input: {
"headers:Authorization": '$Secret{ADMIN_TOKEN}', // Overrides default
["data"] = "value"
}
});
Reference
actions.config() Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
product | string | No | Product tag (defaults to constructor) |
app | string | Yes | App tag |
env | string | No | Environment (dev, stg, prd; defaults to constructor) |
credentials | object | Yes | Flat credentials object |
actions.oauth() Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
product | string | No | Product tag (defaults to constructor) |
app | string | Yes | App tag |
env | string | No | Environment (dev, stg, prd; defaults to constructor) |
tokens | object | Yes | Initial tokens object |
tokens.accessToken | string | Yes | Access token (value or $Secret{}) |
tokens.refreshToken | string | No | Refresh token (value or $Secret{}) |
expiresAt | number | No* | Token expiry (Unix ms timestamp) |
expiresIn | number | No* | Seconds until expiry |
credentials | function | Yes | (tokens) => credentialsObject |
onExpiry | function | Yes | async (tokens) => refreshResult |
refreshBuffer | number | No | Ms before expiry to refresh (default: 60000) |
*Either expiresAt or expiresIn should be provided. If neither is provided, defaults to 1 hour.
onExpiry Return Value
interface IOAuthRefreshResult {
tokens: {
accessToken: string;
refreshToken?: string;
[key: string]: unknown; // Additional token data
};
expiresAt?: number; // Unix ms timestamp
expiresIn?: number; // Seconds until expiry
}