Skip to main content
Actions transform your Guru from a static knowledge base into a dynamic AI agent that can execute code, call APIs, and fetch real-time data. When users ask questions, your Guru automatically triggers relevant actions to provide live, accurate responses.

What Can Actions Do?

Billing & Payments

Look up refunds, invoices, subscriptions, and charges in Stripe, billing systems, or ERPs

Customer Context

Pull account details, entitlements, and plan info from CRM or product databases

System Status

Check Statuspage, Datadog, or monitoring tools for outages and incidents

Order & Inventory

Track shipments, check stock levels, and query warehouse systems in real-time

Ticketing & Jira

Create bugs, update tickets, check SLAs, and manage queue hygiene

Any REST API

Connect to any system with an API — internal tools, third-party services, or custom endpoints

Action Types

TypeDescriptionLatencyBest For
Python CodeExecute custom Python scripts in isolated sandbox containers2-3 secondsComplex logic, data processing, multi-step API calls, AI integrations
API CallMake HTTP requests directly to external APIsVery low (~100ms)Simple REST API lookups, single-endpoint queries
Use API Call for simple lookups (faster response). Use Python Code when you need to process data, call multiple APIs, or add conditional logic.

Scheduled Actions

Actions can run automatically on a schedule using cron expressions — no user question required.
Use CaseCron ExpressionDescription
Daily SLA report0 9 * * *Every day at 9 AM
Hourly status check0 * * * *Every hour
Weekly digest0 9 * * 1Every Monday at 9 AM
Every 15 minutes*/15 * * * *For real-time monitoring
  1. Open the action editor
  2. Scroll to Schedule Configuration
  3. Toggle Enable Scheduling
  4. Enter a cron expression (e.g., 0 9 * * * for daily at 9 AM)
The action will run automatically at the scheduled time. Results are logged in the action history.
Common scheduled action patterns:
  • Morning SLA briefing — Check at-risk tickets before the team starts
  • Incident monitoring — Poll status pages every 15 minutes
  • Usage alerts — Daily check for customers approaching limits
  • Data sync — Periodically update external systems

High-Impact Actions for Common Pain Points

Customer asks: “I requested a refund 3 days ago but still haven’t received it. Order #12345. What’s going on?”

Without Actions

  • Open Stripe dashboard
  • Search for customer email
  • Copy transaction ID
  • Check subscription status in another tab
  • Cross-reference with CRM
  • Type “let me check and get back to you”
  • 5+ minutes per ticket

With Actions

  • Agent asks Guru: “Check refund for order 12345”
  • Action fetches data from Stripe automatically
  • AI responds with status, ETA, and next steps
  • Done in seconds

For Support Agents

Pain: Billing questions are high-volume. Agents waste time hopping between Stripe, ERP, CRM, and email threads just to answer “where is my refund?”User asks: “Where is my refund for order 12345?” or “Can you resend the invoice for December?”Integrations: Stripe, Billing/ERP, CRMResult: Refund state + ETA + invoice link + next step (if failed)Impact: Reduces billing tickets 40%+, eliminates “let me check and get back to you”
Trigger: “When the user asks about a refund status, invoice, or payment”Parameters:
NameTypeWhat to ExtractRequired
order_idStringOrder or invoice numberYes
customer_emailStringCustomer’s email addressNo
Secrets: STRIPE_API_KEY
import os
import requests

order_id = os.environ.get('order_id')
api_key = os.environ.get('STRIPE_API_KEY')

# Search for refunds related to this order
response = requests.get(
    "https://api.stripe.com/v1/refunds",
    auth=(api_key, ''),
    params={"limit": 10}
)

refunds = response.json().get('data', [])
found = None
for r in refunds:
    if order_id in str(r.get('metadata', {})) or order_id in r.get('id', ''):
        found = r
        break

if found:
    status = found['status']
    amount = found['amount'] / 100
    currency = found['currency'].upper()
    print(f"💰 Refund Status: {status.upper()}")
    print(f"💵 Amount: {amount} {currency}")
    if status == 'succeeded':
        print(f"✓ Refund completed - funds returned to original payment method")
        print(f"⏱️ May take 5-10 business days to appear on statement")
    elif status == 'pending':
        print(f"⏳ Refund is processing - typically completes within 5-10 business days")
    else:
        print(f"⚠️ Refund status: {status} - may require manual review")
else:
    print(f"No refund found for order {order_id}")
    print(f"💡 Next step: Check if refund was requested or process new refund")
Pain: “I was charged twice!” claims escalate fast. Agents need exact transaction mapping to prevent unnecessary refunds.User asks: “I see two charges of $49.99. What happened?”Integrations: Payment provider, Subscription ledgerResult: Charge IDs, whether one is auth/hold, whether it was reversed, recommended responseImpact: Prevents unnecessary refunds, reduces escalations, improves customer trust
Trigger: “When the user reports duplicate charges, double billing, or being charged twice”Parameters:
NameTypeWhat to ExtractRequired
customer_emailStringCustomer’s email addressYes
amountNumberThe charge amount in questionNo
Secrets: STRIPE_API_KEY
import os
import requests
from datetime import datetime, timedelta

email = os.environ.get('customer_email')
amount = os.environ.get('amount')
api_key = os.environ.get('STRIPE_API_KEY')

# Get recent charges for this customer
response = requests.get(
    "https://api.stripe.com/v1/charges",
    auth=(api_key, ''),
    params={"limit": 20}
)

charges = response.json().get('data', [])
recent = [c for c in charges if email in str(c.get('billing_details', {}).get('email', ''))]

if len(recent) >= 2:
    print(f"📋 Found {len(recent)} recent charges for {email}:\n")
    for c in recent[:5]:
        amt = c['amount'] / 100
        status = c['status']
        captured = "Captured" if c['captured'] else "Auth Hold (not captured)"
        refunded = " [REFUNDED]" if c['refunded'] else ""
        date = datetime.fromtimestamp(c['created']).strftime('%Y-%m-%d %H:%M')
        print(f"  • ${amt} - {status} - {captured}{refunded}")
        print(f"    ID: {c['id'][:20]}... | Date: {date}\n")
    
    # Check for auth holds
    auth_holds = [c for c in recent if not c['captured']]
    if auth_holds:
        print(f"💡 {len(auth_holds)} charge(s) are authorization holds that will auto-release in 7 days")
    
    refunded = [c for c in recent if c['refunded']]
    if refunded:
        print(f"✓ {len(refunded)} charge(s) already refunded")
else:
    print(f"Only {len(recent)} charge(s) found for {email} - no duplicate detected")
Pain: During outages, agents guess and customers get conflicting answers. Tickets pile up with the same question.User asks: “Your app is down!” or “502 error - is this known?”Integrations: Statuspage, Datadog/New Relic, Incident channelResult: Current incident status, impacted regions/features, workaround, ETA, subscribe linkImpact: Deflects 80%+ of duplicate incident tickets, standardizes incident communication
Trigger: “When the user reports an error, outage, downtime, or asks if there’s a known issue”Parameters:
NameTypeWhat to ExtractRequired
error_codeStringError code or message (e.g., 502, timeout)No
featureStringFeature or service affectedNo
Secrets: STATUSPAGE_API_KEY, STATUSPAGE_PAGE_ID
import os
import requests

api_key = os.environ.get('STATUSPAGE_API_KEY')
page_id = os.environ.get('STATUSPAGE_PAGE_ID')
error_code = os.environ.get('error_code', '')
feature = os.environ.get('feature', '')

# Get current incidents
response = requests.get(
    f"https://api.statuspage.io/v1/pages/{page_id}/incidents/unresolved",
    headers={"Authorization": f"OAuth {api_key}"}
)

incidents = response.json()

if incidents:
    print(f"🚨 ACTIVE INCIDENT(S):\n")
    for inc in incidents[:3]:
        print(f"📌 {inc['name']}")
        print(f"   Status: {inc['status'].upper()}")
        print(f"   Impact: {inc['impact']}")
        if inc.get('incident_updates'):
            latest = inc['incident_updates'][0]
            print(f"   Latest update: {latest['body'][:200]}...")
        print(f"   🔗 Track: {inc['shortlink']}\n")
    
    print(f"💡 Recommended response: We're aware of an issue and actively working on it.")
    print(f"   Share the status link above for real-time updates.")
else:
    print(f"✓ No active incidents on status page")
    print(f"💡 This may be an isolated issue - gather more details:")
    print(f"   • Browser/device?")
    print(f"   • When did it start?")
    print(f"   • Can you share a screenshot?")
Pain: “Why can’t I use feature X?” causes long back-and-forth. Agents ask “What plan are you on?” when they could just look it up.User asks: “Why can’t I use SSO?” or “What’s my seat limit?”Integrations: CRM, Product DB, Feature flagsResult: Plan, entitlements, usage limits, what to upgrade, exact remediationImpact: Fewer escalations to Sales/Engineering, faster resolution
Trigger: “When the user asks about their plan, features, entitlements, limits, or why they can’t access something”Parameters:
NameTypeWhat to ExtractRequired
customer_emailStringCustomer’s email or account IDYes
featureStringFeature they’re asking aboutNo
Secrets: PRODUCT_DB_API_KEY
import os
import requests

email = os.environ.get('customer_email')
feature = os.environ.get('feature', '').lower()
api_key = os.environ.get('PRODUCT_DB_API_KEY')

# Mock product database lookup - replace with your actual API
response = requests.get(
    f"https://api.yourproduct.com/v1/accounts/lookup",
    headers={"Authorization": f"Bearer {api_key}"},
    params={"email": email}
)

if response.status_code == 200:
    data = response.json()
    plan = data.get('plan', 'Free')
    seats_used = data.get('seats_used', 1)
    seats_limit = data.get('seats_limit', 5)
    features = data.get('features', [])
    
    print(f"📋 Account: {email}")
    print(f"📦 Plan: {plan}")
    print(f"👥 Seats: {seats_used}/{seats_limit} used")
    print(f"✨ Features enabled: {', '.join(features) or 'Basic only'}")
    
    if feature:
        if feature in [f.lower() for f in features]:
            print(f"\n✓ '{feature}' IS enabled on this account")
        else:
            print(f"\n✗ '{feature}' is NOT included in {plan} plan")
            print(f"💡 To enable: Upgrade to Pro or Enterprise plan")
else:
    print(f"Account not found for {email}")
Pain: Agents write low-quality Jira tickets. Engineers bounce them back asking for steps, logs, and environment details.Agent asks: “Create a Jira bug for this crash with all the details”Integrations: Jira/Linear, Log aggregator, Error trackingResult: Structured bug (steps, expected/actual, env, severity, customer impact)Impact: Fewer loops with engineering, faster MTTR, happier developers
Trigger: “When the user asks to create a bug report, Jira issue, or ticket for engineering”Parameters:
NameTypeWhat to ExtractRequired
summaryStringBrief description of the bugYes
stepsStringSteps to reproduceNo
customer_emailStringAffected customerNo
priorityStringPriority level (low/medium/high/critical)No
Secrets: JIRA_API_TOKEN, JIRA_EMAIL, JIRA_DOMAIN, JIRA_PROJECT_KEY
import os
import requests
from requests.auth import HTTPBasicAuth

summary = os.environ.get('summary')
steps = os.environ.get('steps', 'Not provided')
customer = os.environ.get('customer_email', 'Unknown')
priority = os.environ.get('priority', 'medium')

api_token = os.environ.get('JIRA_API_TOKEN')
email = os.environ.get('JIRA_EMAIL')
domain = os.environ.get('JIRA_DOMAIN')
project = os.environ.get('JIRA_PROJECT_KEY')

priority_map = {'low': '4', 'medium': '3', 'high': '2', 'critical': '1'}

description = f"""h3. Bug Report

*Reported by:* Support Team
*Affected Customer:* {customer}

h4. Steps to Reproduce
{steps}

h4. Expected Behavior
[To be filled by engineering]

h4. Actual Behavior
{summary}

h4. Environment
* Browser: [Check with customer]
* OS: [Check with customer]
* Account type: [Add from CRM lookup]

h4. Customer Impact
Customer reported this issue via support ticket.
"""

response = requests.post(
    f"https://{domain}.atlassian.net/rest/api/3/issue",
    auth=HTTPBasicAuth(email, api_token),
    headers={"Content-Type": "application/json"},
    json={
        "fields": {
            "project": {"key": project},
            "summary": f"[Support] {summary}",
            "issuetype": {"name": "Bug"},
            "priority": {"id": priority_map.get(priority, '3')},
            "description": {
                "type": "doc",
                "version": 1,
                "content": [{"type": "paragraph", "content": [{"type": "text", "text": description}]}]
            }
        }
    }
)

if response.status_code == 201:
    issue = response.json()
    key = issue['key']
    print(f"✓ Created Jira issue: {key}")
    print(f"🔗 https://{domain}.atlassian.net/browse/{key}")
    print(f"📋 Priority: {priority.upper()}")
else:
    print(f"✗ Failed to create issue: {response.text}")

For Support Leaders

Pain: Missed SLAs create churn and fire drills. Agents don’t notice until it’s too late.Leader asks: “Which tickets will breach SLA in the next 2 hours?”Integrations: Ticketing system, SLA policies, Queue metricsResult: Prioritized list + recommended reassignment + breach countdownImpact: 60% fewer SLA breaches, better WBR metrics, reduced churn
Trigger: “When the user asks about SLA status, at-risk tickets, or potential SLA breaches”Parameters:
NameTypeWhat to ExtractRequired
hours_aheadNumberHow many hours to look aheadNo
teamStringSpecific team or queue to checkNo
Secrets: ZENDESK_API_TOKEN, ZENDESK_SUBDOMAIN, ZENDESK_EMAIL
import os
import requests
from requests.auth import HTTPBasicAuth
from datetime import datetime, timedelta

hours = int(os.environ.get('hours_ahead', 2))
api_token = os.environ.get('ZENDESK_API_TOKEN')
subdomain = os.environ.get('ZENDESK_SUBDOMAIN')
email = os.environ.get('ZENDESK_EMAIL')

# Get tickets with SLA info
response = requests.get(
    f"https://{subdomain}.zendesk.com/api/v2/search.json",
    auth=HTTPBasicAuth(f"{email}/token", api_token),
    params={
        "query": "type:ticket status<solved",
        "sort_by": "created_at",
        "sort_order": "asc",
        "per_page": 50
    }
)

tickets = response.json().get('results', [])

# Filter for at-risk (simplified - real impl would use SLA policies API)
print(f"⚠️ SLA AT-RISK TICKETS (next {hours} hours):\n")

at_risk = []
for t in tickets[:20]:
    created = datetime.fromisoformat(t['created_at'].replace('Z', '+00:00'))
    age_hours = (datetime.now(created.tzinfo) - created).total_seconds() / 3600
    
    # Simple SLA logic - replace with your actual SLA policies
    if t.get('priority') == 'urgent' and age_hours > 1:
        at_risk.append((t, 'CRITICAL', age_hours))
    elif t.get('priority') == 'high' and age_hours > 4:
        at_risk.append((t, 'HIGH', age_hours))

if at_risk:
    for ticket, risk, age in sorted(at_risk, key=lambda x: x[1])[:10]:
        print(f"🔴 #{ticket['id']}: {ticket['subject'][:50]}...")
        print(f"   Risk: {risk} | Age: {age:.1f}h | Priority: {ticket.get('priority', 'normal')}")
        print(f"   Assignee: {ticket.get('assignee_id', 'Unassigned')}")
        print(f"   🔗 https://{subdomain}.zendesk.com/agent/tickets/{ticket['id']}\n")
    
    print(f"💡 Recommended: Reassign top 3 to available senior agents")
else:
    print(f"✓ No tickets at immediate SLA risk")
Pain: During incidents, teams manually compile who is impacted. Customers find out on Twitter before you tell them.Leader asks: “Which customers are impacted by the EU latency issue?”Integrations: Usage telemetry, Region mapping, CRM segmentsResult: Impacted customer list + suggested email template + Zendesk macro updateImpact: Reduces churn by communicating fast and accurately, builds trust
Trigger: “When the user asks which customers are affected by an incident, outage, or issue”Parameters:
NameTypeWhat to ExtractRequired
incident_typeStringType of incident (latency, outage, feature)Yes
regionStringAffected region (EU, US, APAC, etc.)No
Secrets: ANALYTICS_API_KEY
import os
import requests

incident = os.environ.get('incident_type')
region = os.environ.get('region', 'all')
api_key = os.environ.get('ANALYTICS_API_KEY')

# Mock analytics lookup - replace with your telemetry API
print(f"📊 IMPACT ANALYSIS: {incident.upper()}\n")
print(f"Region filter: {region}\n")

# Simulated response - replace with actual API call
print(f"👥 Impacted Customers: 47 accounts")
print(f"   • Enterprise: 12 accounts (prioritize)")
print(f"   • Pro: 28 accounts")
print(f"   • Free: 7 accounts\n")

print(f"📧 SUGGESTED COMMUNICATION:\n")
print(f"Subject: Service Degradation Notice - {incident}")
print(f"""
Dear Customer,

We're currently experiencing {incident} affecting some users in {region}. 
Our team is actively working on resolution.

Current status: Investigating
ETA: Within 2 hours

Track updates: [statuspage link]

We apologize for the inconvenience.
""")
print(f"💡 Actions:")
print(f"   1. Update status page")
print(f"   2. Create Zendesk macro for incoming tickets")
print(f"   3. Notify CSMs for Enterprise accounts")

For Customers (Self-Service)

Pain: “Where is my order?” is the #1 support question. Customers ask constantly, agents guess ETAs, wrong info causes refunds.Customer asks: “Where is my order #12345?” or “When will my package arrive?”Integrations: OMS, Carrier tracking (FedEx/UPS/USPS), Warehouse statusResult: Real tracking summary, last scan location, expected delivery, exception handlingImpact: Deflects 60%+ of WISMO tickets, reduces refunds from “lost” packages
Trigger: “When the user asks about order status, shipping, tracking, or delivery”Parameters:
NameTypeWhat to ExtractRequired
order_idStringOrder number or tracking numberYes
Secrets: SHIPPO_API_KEY
import os
import requests

order_id = os.environ.get('order_id')
api_key = os.environ.get('SHIPPO_API_KEY')

# Using Shippo for multi-carrier tracking - replace with your provider
response = requests.get(
    f"https://api.goshippo.com/tracks/{order_id}",
    headers={"Authorization": f"ShippoToken {api_key}"}
)

if response.status_code == 200:
    data = response.json()
    status = data.get('tracking_status', {})
    
    print(f"📦 Order: {order_id}")
    print(f"🚚 Carrier: {data.get('carrier', 'Unknown')}")
    print(f"📍 Status: {status.get('status', 'Unknown').upper()}")
    print(f"📍 Location: {status.get('location', {}).get('city', 'In transit')}")
    
    eta = data.get('eta')
    if eta:
        print(f"📅 Expected delivery: {eta}")
    
    # Show tracking history
    history = data.get('tracking_history', [])[:3]
    if history:
        print(f"\n📋 Recent updates:")
        for event in history:
            print(f"   • {event.get('status_details', 'Update')} - {event.get('status_date', '')[:10]}")
else:
    print(f"📦 Order {order_id}: Processing")
    print(f"💡 Tracking will be available once shipped (usually within 24-48 hours)")
Pain: Customers can’t find basic account info. “What plan am I on?” “When does my trial end?” flood support.Customer asks: “What plan am I on?” or “When does my subscription renew?”Integrations: Billing system, Subscription managementResult: Plan details, renewal date, usage stats, upgrade optionsImpact: Deflects 40% of account-related tickets, enables self-service upgrades
Trigger: “When the user asks about their account, plan, subscription, billing, or renewal”Parameters:
NameTypeWhat to ExtractRequired
customer_emailStringCustomer’s email (from session/memory)Yes
Secrets: STRIPE_API_KEY
import os
import requests
from datetime import datetime

email = os.environ.get('customer_email')
api_key = os.environ.get('STRIPE_API_KEY')

# Look up customer in Stripe
response = requests.get(
    "https://api.stripe.com/v1/customers/search",
    auth=(api_key, ''),
    params={"query": f'email:"{email}"'}
)

customers = response.json().get('data', [])

if customers:
    customer = customers[0]
    
    # Get subscriptions
    sub_response = requests.get(
        "https://api.stripe.com/v1/subscriptions",
        auth=(api_key, ''),
        params={"customer": customer['id'], "limit": 1}
    )
    
    subs = sub_response.json().get('data', [])
    
    print(f"👤 Account: {email}\n")
    
    if subs:
        sub = subs[0]
        plan = sub.get('items', {}).get('data', [{}])[0].get('price', {}).get('nickname', 'Standard')
        status = sub['status']
        period_end = datetime.fromtimestamp(sub['current_period_end'])
        
        print(f"📦 Plan: {plan}")
        print(f"📊 Status: {status.upper()}")
        print(f"📅 Renews: {period_end.strftime('%B %d, %Y')}")
        
        if sub.get('cancel_at_period_end'):
            print(f"⚠️ Cancels at period end")
    else:
        print(f"📦 Plan: Free")
        print(f"💡 Upgrade anytime at yourapp.com/pricing")
else:
    print(f"No account found for {email}")
    print(f"💡 Sign up at yourapp.com/signup")
Pain: “Is this in stock?” questions are endless. Static KB answers are outdated by the time they’re published.Customer asks: “Is the Blue Widget in stock in size XL?”Integrations: Inventory management, Warehouse systemsResult: Real-time stock level, shipping estimate, alternatives if out of stockImpact: Reduces pre-sales questions 50%, increases conversions with accurate info
Trigger: “When the user asks about stock, inventory, availability, or if a product is in stock”Parameters:
NameTypeWhat to ExtractRequired
product_nameStringProduct name or SKUYes
variantStringSize, color, or other variantNo
Secrets: INVENTORY_API_KEY
import os
import requests

product = os.environ.get('product_name')
variant = os.environ.get('variant', 'default')
api_key = os.environ.get('INVENTORY_API_KEY')

# Replace with your inventory API
response = requests.get(
    "https://api.yourinventory.com/v1/stock",
    headers={"Authorization": f"Bearer {api_key}"},
    params={"product": product, "variant": variant}
)

if response.status_code == 200:
    data = response.json()
    stock = data.get('quantity', 0)
    
    print(f"📦 {product}" + (f" ({variant})" if variant != 'default' else ""))
    
    if stock > 10:
        print(f"✅ In Stock: {stock} available")
        print(f"🚚 Ships within 1-2 business days")
    elif stock > 0:
        print(f"⚠️ Low Stock: Only {stock} left!")
        print(f"🚚 Order now - ships within 1-2 business days")
    else:
        restock = data.get('restock_date', 'soon')
        print(f"❌ Currently Out of Stock")
        print(f"📅 Expected back: {restock}")
        
        # Suggest alternatives
        alts = data.get('alternatives', [])
        if alts:
            print(f"\n💡 Similar items in stock:")
            for alt in alts[:3]:
                print(f"   • {alt}")
else:
    print(f"Product not found. Try searching on our website.")
Actions
Each Guru has a limit on the number of actions based on your plan (shown as “5 of 10 actions used”).

How Actions Work

1

Trigger Detection

The Guru checks if the user’s question matches your defined trigger conditions.
2

Parameter Extraction

Parameters are automatically extracted from the user’s question based on your descriptions.
3

Execution

Python code runs in an isolated container, or an HTTP request is made to your API endpoint.
4

Response Handling

The AI interprets and presents the results to the user.
Actions only execute if all required parameters can be extracted from the user’s question (or have default values).

Creating an Action

Empty Actions

Step 1: Basic Information

Action Basic Configuration
FieldDescriptionExample
Action NameDescriptive name for your action”Get Weather Data”
When to TriggerSpecific conditions for when this action should run”When the user asks about current weather for a city”
Be specific with trigger conditions. Instead of “When asking about data”, use “When asking about current weather conditions for a specific location”.

Step 2: Define Parameters

Action Parameters Configuration
Parameters are values extracted from the user’s question:
FieldDescription
NameLetters and underscores only (e.g., city, user_id)
TypeString, Number, or Boolean
What to ExtractClear description for extraction
RequiredMust be provided for action to run
Default ValueFallback if not extracted
How to use parameters:
  • Python Code: os.environ.get('city')
  • API Call: https://api.example.com/weather/{city}

Step 3: Configure Action Type

Choose Python Code or API Call and configure accordingly. See Python Code Actions or API Call Actions below.

Step 4: Testing

Before enabling the action, use Test Action to run it with sample parameters. This verifies that parameters, secrets, and the action logic work as expected. Check that the output looks correct and that secrets are masked in the result.

Python Code Actions

Python Code Configuration
Execute custom Python code in isolated Docker containers. Secrets and parameters are injected as environment variables.

Isolated Execution

Each run uses a fresh container, destroyed after use

Pre-installed Libraries

pandas, numpy, requests, openai, and more ready to use

Results via stdout

Your print() output is captured and returned

Secrets Protection

All secrets are masked in output automatically

Sandbox Environment

Python code runs inside an isolated sandbox container: Base Image: python:3.13-slim with git and curl installed
anthropic==0.76.0
beautifulsoup4==4.14.3
httpx==0.28.1
instructor==1.14.3
lxml==6.0.2
matplotlib==3.10.8
numpy==2.2.3
openai==2.15.0
opencv-python==4.12.0.88
pandas==2.3.3
pillow==12.1.0
pydantic==2.12.5
requests==2.32.5
scikit-learn==1.8.0
scipy==1.17.0
seaborn==0.13.2
urllib3==2.6.3
You cannot install additional packages at runtime. If you need a package that isn’t listed, contact us.

Execution Limits

ResourceLimit
Memory512MB
CPU1 core
Max Processes128
Timeout30 seconds
Temp Storage (/tmp)100MB

Security Restrictions

RestrictionDescription
Read-only filesystemOnly /tmp is writable
No binary execution/tmp mounted with noexec flag
Dropped capabilitiesAll Linux capabilities removed
Network accessAllowed for API calls

Accessing Parameters and Secrets

Parameters and secrets are injected as environment variables:
import os

# Access a parameter extracted from user's question
username = os.environ.get('USERNAME')

# Access a secret configured in Guru Settings
api_key = os.environ.get('MY_API_KEY')

print(f"Fetching data for {username}...")
The os module is restricted. Only os.environ, os.environ.get(), and os.getenv() are allowed. Other os functions (os.system(), os.popen(), file operations) are blocked.

Basic Pattern

Every Python action follows the same pattern:
import os
import requests

# 1. Get parameters (extracted from user's question)
order_id = os.environ.get('order_id')

# 2. Get secrets (configured in Guru Settings)
api_key = os.environ.get('STRIPE_API_KEY')

# 3. Call your API
response = requests.get(
    "https://api.stripe.com/v1/refunds",
    auth=(api_key, ''),
    params={"limit": 10}
)

# 4. Process and print results (this is what the AI uses)
data = response.json()
print(f"Found {len(data.get('data', []))} refunds")
See High-Impact Actions for Common Pain Points for 10 production-ready examples with real API integrations.

API Call Actions

API Call Configuration
Configure HTTP requests to external APIs:
FieldDescriptionExample
HTTP MethodGET, POST, PUT, PATCH, DELETEGET
Endpoint URLAPI endpoint with parameter placeholdershttps://api.github.com/users/{username}
HeadersAuthentication and content typeAuthorization: Bearer {API_KEY}
Request BodyJSON payload (for POST/PUT/PATCH)See below
API Call Request Body
Request Body Example:
{
  "user_id": "{user_id}",
  "action": "update",
  "data": "{payload}"
}
Use {parameter_name} or {SECRET_NAME} syntax anywhere in the URL, headers, or body.

Managing Actions

Actions Management Dashboard

Dashboard Actions

ActionDescription
HistoryView execution history of all actions
ImportImport action from JSON file
Create an ActionCreate new action from scratch

Per-Action Menu (⋮)

ActionDescription
EditModify configuration
ExportDownload as JSON file
Trigger ActionRun manually
DisableTemporarily disable
DeletePermanently remove
If a secret used by an action is deleted, that action will be automatically disabled.

How Actions Appear in Answers

Action Reference
When an action is used, it’s shown with the action name in the response.

Secrets Management

Secrets
Secrets are encrypted credentials (API keys, tokens, passwords) stored in Guru Settings → Secrets.
FeatureDescription
EncryptionAES-256 encryption at rest
MaskingAutomatically masked in all output
Python Accessos.environ.get('SECRET_NAME')
API Call Access{SECRET_NAME} syntax
Secret names cannot conflict with parameter names. Deleting a secret automatically disables all actions using it.

Best Practices

AvoidDo Instead
”When asking about data""When asking about current weather conditions for a specific location"
"When user needs info""When user asks for a GitHub user’s profile information”
AvoidDo Instead
id - “The ID”user_id - “The unique identifier for the GitHub user whose profile information is being requested”
city - “City”city - “The name of the city to get weather data for (e.g., London, New York)”
  • Always test actions with various parameter combinations
  • Verify error handling for missing or invalid parameters
  • Check that secrets are properly masked in output
  • Each action should do one specific task
  • Avoid actions that try to do too many things
  • Create multiple simple actions instead of one complex one

Next Steps