Skip to main content
Use this path with the package published from ara-python-sdk.
Python SDK links: GitHub repository · ara-sdk on PyPI

1) Install

pip install ara-sdk

2) Create app.py

from ara_sdk import App, Secret, fastapi_endpoint, invoke, runtime, schedule

app = App(
    "investor-meeting-booking",
    interfaces={
        # Optional: set true to allow owner connector tools in app-runtime task/pipeline turns.
        # Default is false (isolated by default).
        "inherit_owner_tools": False,
    },
    runtime_profile=runtime(
        env={"APP_MODE": "production"},
        secrets=[
            Secret.from_name("provider-shared", required_keys=["OPENAI_API_KEY"]),
            Secret.from_dotenv(filename=".env.local"),
        ],
    ),
)

@app.tool()
def send_email(to: str, subject: str, body: str) -> dict:
    """Send one email."""
    return {"ok": True, "to": to}

@app.agent(
    entrypoint=True,
    skills=["send_email", "automation_create", "automation_list", "automation_delete"],
    schedules=[
        schedule.cron(
            expr="0 13 * * 1-5",
            timezone="UTC",
            run=invoke.agent("booking_coordinator", input={"message": "Send pending followups."}),
        )
    ],
)
def booking_coordinator(input: dict) -> str:
    """Coordinate scheduling requests."""
    message = str(input.get("message") or "")
    return f"Coordinate scheduling requests. User input: {message}"


@app.agent()
@fastapi_endpoint(method="POST", path="/webhooks/inbound", auth="header")
def inbound_webhook_agent(input: dict) -> str:
    """Handle inbound webhook payloads sent to the endpoint gateway."""
    event = input.get("input") if isinstance(input, dict) else None
    return f"Process webhook event: {event}"
App(...) accepts the project name either positionally (App("my-app")) or as a keyword (App(project_name="my-app")).
Note Secret.from_dict(...) and Secret.from_dotenv(...) no longer accept name= or required_keys= parameters. Use Secret.from_name(...) when you need an explicit named secret reference.
The standalone ara CLI discovers app = App(...) automatically. You do not need to add any if __name__ == "__main__" runner in your app script.

3) Configure environment

ara auth login
export OPENAI_API_KEY="YOUR_PROVIDER_KEY"
For CI/headless workflows, you can still set ARA_API_KEY manually. Optional overrides:
  • ARA_API_BASE_URL (defaults to production API base)
  • ARA_RUNTIME_KEY (runtime bearer key for run/events/run-async/run-status/logs)
  • ARA_APP_HEADER_KEY (runtime header key for X-Ara-App-Key auth mode)
ara auth login stores control-plane credentials in ~/.ara/credentials.json and auto-refreshes JWTs when needed. ara auth login uses polling-based PKCE OAuth (Google) and does not require a localhost callback listener. If your Ara account is Google OAuth-only, store an existing control-plane key instead: ara auth login --api-key <ARA_API_KEY>. To be explicit, you can pass: ara auth login --auth-flow poll. To rotate your API key (invalidates the current one):
ara auth rotate
If you use ARA_API_KEY as an env var, update it with the new key from the output. If you logged in via ara auth login, credentials are updated automatically.

4) Run commands

ara deploy app.py
ara auth whoami
ara setup-auth app.py
ara up app.py
ara start
ara run app.py --agent booking_coordinator --message "Need 3 slots next week"
ara run-async app.py --agent booking_coordinator --message "Need 3 slots next week" --response-mode poll
ara run-status app.py --run-id <RUN_ID>
ara status
ara stop
ara logs app.py | tee app.logs
ara events app.py --agent booking_coordinator --event-type channel.web.inbound --channel web --message "hello"
ara setup app.py
ara invite app.py --email teammate@example.com --role operator
deploy returns a runtime_key in command output. Export it as ARA_RUNTIME_KEY (or pass --runtime-key) for runtime commands. setup-auth returns an app_header_key you can pass as --app-header-key (or export ARA_APP_HEADER_KEY) for app-header-key auth. No local runtime/auth key files are required. If you declare local secret sources, deploy also syncs them through /apps/{app_id}/secrets. Use ara setup app.py to inspect setup.runtime.inherit_owner_tools and verify connector inheritance.

Runtime logs

ara logs is a realtime stream and does not backfill historical events. Start logs first, then trigger run/run-async in another terminal.
ara logs app.py | tee app.logs
If no lines appear, confirm you passed a valid runtime/app-header key and that the app/runtime is emitting log events for the run path you are testing.

FastAPI endpoint setup metadata

When you declare @fastapi_endpoint(...), ara setup app.py includes endpoint metadata under:
  • setup.webhook.fastapi_endpoints[].endpoint_url
  • setup.webhook.fastapi_endpoints[].slug_endpoint_url
  • setup.webhook.fastapi_endpoints[].auth.mode (none, header, bearer, hmac)
runtime_input forwards HTTP body data as input so agent prompts can read request payloads without key collisions.

Decorator-based schedules

You can also attach schedules directly to declarations:
@app.schedule(cron="0 9 * * 1-5", at=["14:30", "2026-04-11T16:45:00Z"])
@app.agent()
def ops_agent(input: dict) -> str:
    return "Run scheduled checks."

@app.schedule(cron="0 * * * *")
@app.tool()
def cleanup_cache(path: str = "/tmp/cache") -> dict:
    return {"ok": True, "path": path}
When at includes ISO one-shot timestamps, the SDK represents them as cron + one-shot metadata. If a timestamp slot is already in the past at deploy time, the first execution can occur on the next yearly recurrence.

Command summary

ara deploy app.py [--activate true|false] [--key-name NAME] [--rpm 60] [--warm true|false] [--warm-agent AGENT_ID] [--on-existing error]
ara up app.py [--activate true|false] [--key-name NAME] [--rpm 60] [--warm true|false] [--warm-agent AGENT_ID] [--on-existing error]
ara auth login [--api-base-url URL] [--api-key KEY] [--provider google] [--timeout-seconds 180] [--no-browser] [--supabase-url URL] [--supabase-anon-key KEY]
ara auth whoami [--api-base-url URL]
ara auth logout
ara start
ara status
ara stop
ara setup-auth app.py
ara run app.py [--agent AGENT_ID] [--message TEXT] [--input key=value ...] [--runtime-key KEY] [--app-header-key KEY]
ara run-async app.py [--agent AGENT_ID] [--message TEXT] [--input key=value ...] [--response-mode poll|webhook] [--callback-url URL] [--callback-secret SECRET] [--runtime-key KEY] [--app-header-key KEY]
ara run-status app.py --run-id RUN_ID [--runtime-key KEY] [--app-header-key KEY]
ara logs app.py [--runtime-key KEY] [--app-header-key KEY]
ara events app.py [--agent AGENT_ID] [--event-type TYPE] [--channel CHANNEL] [--source SOURCE] [--message TEXT] [--input key=value ...] [--metadata key=value ...] [--idempotency-key KEY] [--runtime-key KEY] [--app-header-key KEY]
ara invite app.py --email user@example.com [--role viewer|operator|editor] [--expires-hours 168]
ara setup app.py
deploy/up update existing apps by default. Use --on-existing error only when you want deploy to fail if the app already exists. For exact behavior and edge cases, see /sdk/reference.

Run maintained examples

The full maintained examples live in ara-python-sdk/examples/: Notes:
  • 01-c uses a local custom @skill_handler decorator inside the example; it is not an SDK primitive.
  • 02 frontend requires VITE_ARA_APP_ID and VITE_ARA_RUNTIME_KEY populated from ara setup-auth 02-canonical-email-chat-cron.py.
  • 03 requires a local webhook receiver plus ngrok.
  • 06 demonstrates programmatic secret updates before redeploy.
  • 07 demonstrates static/declarative scheduling with @app.schedule(...).
  • 07b demonstrates runtime-managed scheduling via automation_create (fire-and-forget).
  • 07c demonstrates the minimal runtime automation lifecycle (create -> list -> delete -> list).

5) Debug your app

Use AraRuntimeClient to interact with your session programmatically — send messages, run tools, and inspect state without the UI.
from ara_sdk import AraRuntimeClient

rt = AraRuntimeClient.from_env()

# Send a message through the full agent loop
result = rt.chat(message="What tools do you have?")
print(result["text"])
print(result["tool_calls"])

# Read a file from the sandbox
user_md = rt.execute_tool(
    session_id="sess-...",
    tool_name="read_file",
    args={"path": "/root/.ara/memory/USER.md"},
)
See /sdk/runtime-client for the full guide.