Running Actions
Once you have Actions imported into an App, you can execute them using ductape.api.run(). This function handles authentication, environment switching, and request formatting automatically.
Prerequisites
Before running Actions, ensure you have:
- The Ductape SDK installed and initialized
- An App with imported Actions
- A Product that connects to the App
- TypeScript
- Java
- Go
- .NET
import Ductape from '@ductape/sdk';
const ductape = new Ductape({
accessKey: 'your-access-key',
product: 'my-product',
env: 'dev',
});
import app.ductape.sdk.Ductape;
import app.ductape.sdk.core.EnvType;
import app.ductape.sdk.core.RequestContext;
Map<String, Object> ductape = new Ductape(Map.of(
"accessKey", "your-access-key",
"product", "my-product",
"env", "dev"
));
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
}
using Ductape.Sdk;
using Ductape.Sdk.Core;
var ductape = new Ductape({
["accessKey"] = "your-access-key",
["product"] = "my-product",
["env"] = "dev",
});
Quick Example
- TypeScript
- Java
- Go
- .NET
const result = await ductape.api.run({
app: 'stripe-app',
action: 'create_customer',
input: {
email: 'john@example.com',
name: 'John Doe'
}
});
console.log(result); // { id: 'cus_xxx', email: 'john@example.com', ... }
Map<String, Object> result = ductape.api().run(Map<String, Object>.of(
"app", "stripe-app",
"action", "create_customer",
input: Map.of(
"email", "john@example.com",
"name", "John Doe"
)
));
System.out.println(result); // Map.of( "id", "cus_xxx", "email", "john@example.com", ... )
import "context"
result := client.Api.Run(ctx, map[string]any{
"app": "stripe-app",
"action": "create_customer",
input: {
"email": "john@example.com",
"name": "John Doe"
}
});
fmt.Println(result); // { "id": "cus_xxx", "email": "john@example.com", ... }
var result = await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "stripe-app",
["action"] = "create_customer",
input: {
["email"] = "john@example.com",
["name"] = "John Doe"
}
});
Console.WriteLine(result); // { ["id"] = "cus_xxx", ["email"] = "john@example.com", ... }
How It Works
Set product and env on the Ductape constructor (see runtime defaults). Each call then needs:
- app - The connected app's access tag (e.g.,
stripe-app,twilio-app) - action (or event) - The action to trigger (e.g.,
create_customer,send_sms) - input - Data to send (auto-resolved to body, query, params, or headers from the action schema)
You can still pass product or env on a single call to override the constructor for that call only.
Flat Input Format
The SDK uses a flat input format that automatically resolves your fields to the correct location (body, params, query, or headers) based on the action's schema definition.
- TypeScript
- Java
- Go
- .NET
// Simple and clean - fields are auto-resolved
input: {
email: 'john@example.com', // auto-resolves to body.email
name: 'John Doe', // auto-resolves to body.name
userId: '123' // auto-resolves to params.userId
}
// Simple and clean - fields are auto-resolved
input: Map.of(
"email", "john@example.com", // auto-resolves to body.email
"name", "John Doe", // auto-resolves to body.name
"userId", "123" // auto-resolves to params.userId
)
// Simple and clean - fields are auto-resolved
input: {
"email": "john@example.com", // auto-resolves to body.email
"name": "John Doe", // auto-resolves to body.name
"userId": "123" // auto-resolves to params.userId
}
// Simple and clean - fields are auto-resolved
input: {
["email"] = "john@example.com", // auto-resolves to body.email
["name"] = "John Doe", // auto-resolves to body.name
["userId"] = "123" // auto-resolves to params.userId
}
Handling Conflicts with Prefixes
If the same key exists in multiple locations (e.g., id in both params and body), use prefix syntax to specify the exact location:
- TypeScript
- Java
- Go
- .NET
input: {
'params:id': 'user_123', // explicitly goes to params.id
'body:id': 'item_456', // explicitly goes to body.id
'query:id': 'search_789', // explicitly goes to query.id
'headers:X-Request-ID': 'req_abc' // explicitly goes to headers
}
input: Map.of(
'params:id': 'user_123', // explicitly goes to params.id
'body:id': 'item_456', // explicitly goes to body.id
'query:id': 'search_789', // explicitly goes to query.id
'headers:X-Request-ID': 'req_abc' // explicitly goes to headers
)
input: {
'params:id': 'user_123', // explicitly goes to params.id
'body:id': 'item_456', // explicitly goes to body.id
'query:id': 'search_789', // explicitly goes to query.id
'headers:X-Request-ID': 'req_abc' // explicitly goes to headers
}
input: {
'params:id': 'user_123', // explicitly goes to params.id
'body:id': 'item_456', // explicitly goes to body.id
'query:id': 'search_789', // explicitly goes to query.id
"headers:X-Request-ID": 'req_abc' // explicitly goes to headers
}
Available Prefixes
| Prefix | Target Location | Example |
|---|---|---|
body: | Request body | 'body:amount': 1000 |
params: | Route parameters | 'params:userId': '123' |
query: | Query parameters | 'query:limit': 10 |
headers: | HTTP headers | 'headers:Authorization': 'Bearer ...' |
More Examples
Sending data in the request body
- TypeScript
- Java
- Go
- .NET
await ductape.api.run({
app: 'stripe',
action: 'create_payment_intent',
input: {
amount: 2000,
currency: 'usd',
customer: 'cus_123'
}
});
ductape.api().run(Map<String, Object>.of(
"app", "stripe",
"action", "create_payment_intent",
input: Map.of(
"amount", 2000,
"currency", "usd",
"customer", "cus_123"
)
));
import "context"
client.Api.Run(ctx, map[string]any{
"app": "stripe",
"action": "create_payment_intent",
input: {
"amount": 2000,
"currency": "usd",
"customer": "cus_123"
}
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "stripe",
["action"] = "create_payment_intent",
input: {
["amount"] = 2000,
["currency"] = "usd",
["customer"] = "cus_123"
}
});
Using query parameters
- TypeScript
- Java
- Go
- .NET
await ductape.api.run({
app: 'hubspot',
action: 'search_contacts',
input: {
email: 'jane@example.com',
limit: 10
}
});
ductape.api().run(Map<String, Object>.of(
"app", "hubspot",
"action", "search_contacts",
input: Map.of(
"email", "jane@example.com",
"limit", 10
)
));
import "context"
client.Api.Run(ctx, map[string]any{
"app": "hubspot",
"action": "search_contacts",
input: {
"email": "jane@example.com",
"limit": 10
}
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "hubspot",
["action"] = "search_contacts",
input: {
["email"] = "jane@example.com",
["limit"] = 10
}
});
With route parameters
- TypeScript
- Java
- Go
- .NET
await ductape.api.run({
app: 'shopify',
action: 'get_product',
input: {
productId: '12345'
}
});
ductape.api().run(Map<String, Object>.of(
"app", "shopify",
"action", "get_product",
input: Map.of(
"productId", "12345"
)
));
import "context"
client.Api.Run(ctx, map[string]any{
"app": "shopify",
"action": "get_product",
input: {
"productId": "12345"
}
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "shopify",
["action"] = "get_product",
input: {
["productId"] = "12345"
}
});
With custom headers
- TypeScript
- Java
- Go
- .NET
await ductape.api.run({
app: 'internal-api',
action: 'fetch_user',
input: {
userId: '456',
'headers:X-Request-ID': 'req_abc123'
}
});
ductape.api().run(Map<String, Object>.of(
"app", "internal-api",
"action", "fetch_user",
input: Map.of(
"userId", "456",
'headers:X-Request-ID': 'req_abc123'
)
));
import "context"
client.Api.Run(ctx, map[string]any{
"app": "internal-api",
"action": "fetch_user",
input: {
"userId": "456",
'headers:X-Request-ID': 'req_abc123'
}
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "internal-api",
["action"] = "fetch_user",
input: {
["userId"] = "456",
"headers:X-Request-ID": 'req_abc123'
}
});
Mixed input with explicit prefixes
When you have fields that could conflict, use prefixes for clarity:
- TypeScript
- Java
- Go
- .NET
await ductape.api.run({
app: 'order-service',
action: 'update_order',
input: {
'params:orderId': 'order_123', // Route: /orders/:orderId
status: 'shipped', // Body field
tracking_number: 'TRK456', // Body field
'headers:X-Idempotency-Key': 'unique_key_789'
}
});
ductape.api().run(Map<String, Object>.of(
"app", "order-service",
"action", "update_order",
input: Map.of(
'params:orderId': 'order_123', // Route: /orders/:orderId
"status", "shipped", // Body field
"tracking_number", "TRK456", // Body field
'headers:X-Idempotency-Key': 'unique_key_789'
)
));
import "context"
client.Api.Run(ctx, map[string]any{
"app": "order-service",
"action": "update_order",
input: {
'params:orderId': 'order_123', // Route: /orders/:orderId
"status": "shipped", // Body field
"tracking_number": "TRK456", // Body field
'headers:X-Idempotency-Key': 'unique_key_789'
}
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "order-service",
["action"] = "update_order",
input: {
'params:orderId': 'order_123', // Route: /orders/:orderId
["status"] = "shipped", // Body field
["tracking_number"] = "TRK456", // Body field
"headers:X-Idempotency-Key": 'unique_key_789'
}
});
With shared configuration
Use actions.config() to set credentials once and reuse them across multiple action calls.
Recommended pattern: Define your app config once and spread it:
- TypeScript
- Java
- Go
- .NET
// Define app configurations
const stripeConfig = { app: 'stripe' };
const twilioConfig = { app: 'twilio' };
// Set credentials using the config
ductape.api.config({
...stripeConfig,
credentials: {
'headers:Authorization': '$Secret{STRIPE_API_KEY}',
}
});
ductape.api.config({
...twilioConfig,
credentials: {
'headers:Authorization': '$Secret{TWILIO_AUTH_TOKEN}',
}
});
// Now 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({
...stripeConfig,
action: 'list_customers',
input: { limit: 10 }
});
await ductape.api.run({
...twilioConfig,
action: 'send_sms',
input: { to: '+1234567890', body: 'Hello!' }
});
// Define app configurations
Map<String, Object> stripeConfig = Map.of( "app", "stripe" );
Map<String, Object> twilioConfig = Map.of( "app", "twilio" );
// Set credentials using the config
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)'
)
));
// Now 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(
...stripeConfig,
"action", "list_customers",
input: Map.of( "limit", 10 )
));
ductape.api().run(Map<String, Object>.of(
...twilioConfig,
"action", "send_sms",
input: Map.of( "to", "+1234567890", "body", "Hello!" )
));
import "context"
// Define app configurations
stripeConfig := map[string]any{ "app": "stripe" };
twilioConfig := map[string]any{ "app": "twilio" };
// Set credentials using the config
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}',
}
});
// Now 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{
...stripeConfig,
"action": "list_customers",
input: { "limit": 10 }
});
client.Api.Run(ctx, map[string]any{
...twilioConfig,
"action": "send_sms",
input: { "to": "+1234567890", "body": "Hello!" }
});
// Define app configurations
var stripeConfig = new Dictionary<string, object?>
{ ["app"] = "stripe" };
var twilioConfig = new Dictionary<string, object?>
{ ["app"] = "twilio" };
// Set credentials using the config
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}',
}
});
// Now 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?>
{
...stripeConfig,
["action"] = "list_customers",
input: { ["limit"] = 10 }
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
...twilioConfig,
["action"] = "send_sms",
input: { ["to"] = "+1234567890", ["body"] = "Hello!" }
});
This pattern keeps your code DRY and makes it easy to switch environments:
- TypeScript
- Java
- Go
- .NET
// Environment-based configs
const stripe = {
dev: { app: 'stripe' },
prd: { app: 'stripe' },
};
// Use the appropriate config
const env = process.env.NODE_ENV === 'production' ? 'prd' : 'dev';
ductape.api.config({
...stripe[env],
credentials: { 'headers:Authorization': '$Secret{STRIPE_API_KEY}' }
});
// Environment-based configs
Map<String, Object> stripe = Map.of(
dev: Map.of( "app", "stripe" ),
prd: Map.of( "app", "stripe" )
);
// Use the appropriate config
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"
// Environment-based configs
stripe := map[string]any{
dev: { "app": "stripe" },
prd: { "app": "stripe" },
};
// Use the appropriate config
env := os.Getenv("NODE_ENV") === 'production' ? 'prd' : 'dev';
client.Api.Config(ctx, map[string]any{
...stripe[env],
credentials: { 'headers:Authorization': '$Secret{STRIPE_API_KEY}' }
});
// Environment-based configs
var stripe = new Dictionary<string, object?>
{
dev: { ["app"] = "stripe" },
prd: { ["app"] = "stripe" },
};
// Use the appropriate config
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}' }
});
With session tracking
Use sessions to inject user-specific data dynamically:
- TypeScript
- Java
- Go
- .NET
await ductape.api.run({
app: 'analytics',
action: 'get_user_stats',
input: {
userId: '$Session{user}{id}'
},
session:'user-session:eyJhbGciOi...'
});
ductape.api().run(Map<String, Object>.of(
"app", "analytics",
"action", "get_user_stats",
input: Map.of(
"userId", "$SessionMap.of(user)Map.of(id)"
),
"session", "user-session:eyJhbGciOi..."
));
import "context"
client.Api.Run(ctx, map[string]any{
"app": "analytics",
"action": "get_user_stats",
input: {
"userId": "$Session{user}{id}"
},
"session": "user-session:eyJhbGciOi..."
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "analytics",
["action"] = "get_user_stats",
input: {
["userId"] = "$Session{user}{id}"
},
["session"] = "user-session:eyJhbGciOi..."
});
With caching and retries
- TypeScript
- Java
- Go
- .NET
await ductape.api.run({
app: 'products-api',
action: 'list_products',
input: {
category: 'electronics'
},
cache: 'products-list', // Cache the response
retries: 3 // Retry up to 3 times on failure
});
ductape.api().run(Map<String, Object>.of(
"app", "products-api",
"action", "list_products",
input: Map.of(
"category", "electronics"
),
"cache", "products-list", // Cache the response
"retries", 3 // Retry up to 3 times on failure
));
import "context"
client.Api.Run(ctx, map[string]any{
"app": "products-api",
"action": "list_products",
input: {
"category": "electronics"
},
"cache": "products-list", // Cache the response
"retries": 3 // Retry up to 3 times on failure
});
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "products-api",
["action"] = "list_products",
input: {
["category"] = "electronics"
},
["cache"] = "products-list", // Cache the response
["retries"] = 3 // Retry up to 3 times on failure
});
Optional Parameters
| Parameter | What it does |
|---|---|
cache | Cache tag to store the response for reuse |
retries | Number of retry attempts if the action fails |
session | Session object for dynamic value injection |
Shared Configuration
actions.config()
Set shared credentials for an app. Product and env come from the Ductape constructor unless you pass them to override.
- TypeScript
- Java
- Go
- .NET
ductape.api.config({
app: string, // App tag
credentials: { // Credentials in flat input format
'headers:Authorization': 'Bearer xxx',
'headers:X-API-Key': 'key_xxx',
// ... any other credentials
}
});
ductape.api().config(Map<String, Object>.of(
app: string, // App tag
credentials: Map.of( // Credentials in flat input format
'headers:Authorization': 'Bearer xxx',
'headers:X-API-Key': 'key_xxx',
// ... any other credentials
)
));
import "context"
client.Api.Config(ctx, map[string]any{
app: string, // App tag
credentials: { // Credentials in flat input format
'headers:Authorization': 'Bearer xxx',
'headers:X-API-Key': 'key_xxx',
// ... any other credentials
}
});
ductape.Api.Config(new Dictionary<string, object?>
{
app: string, // App tag
credentials: { // Credentials in flat input format
"headers:Authorization": 'Bearer xxx',
"headers:X-API-Key": 'key_xxx',
// ... any other credentials
}
});
Key behaviors:
- Credentials are stored in memory for the SDK instance
- User input takes precedence over shared credentials (can override)
- Use prefix syntax for headers:
'headers:Authorization' - Works with
$Secret{}placeholders for secure credential injection
Reference
IActionProcessorInput
interface IActionProcessorInput {
product?: string; // optional; defaults to constructor
env?: string; // optional; defaults to constructor
app: string;
action: string;
input: IFlatInput | IActionRequest; // Flat or structured format
cache?: string;
retries?: number;
session?: string;
}
IFlatInput (Recommended)
// Flat input - auto-resolved from action schema
type IFlatInput = Record<string, unknown>;
// Example
const input: IFlatInput = {
amount: 1000, // auto-resolves to body.amount
currency: 'usd', // auto-resolves to body.currency
'params:id': 'user_123' // explicit: goes to params.id
};
IActionRequest (Structured Format)
The structured format is still supported for backwards compatibility:
interface IActionRequest {
query?: Record<string, unknown>; // URL query parameters
params?: Record<string, unknown>; // Route parameters
body?: Record<string, unknown>; // Request body
headers?: Record<string, unknown>; // HTTP headers
}