This guide walks you through building an Autohive integration from scratch. By the end, you'll have a working integration with configuration, action handlers, and tests.
- Python 3.13+ — the SDK enforces this version
- pip or uv for package management
- A code editor
Every Autohive integration lives in its own directory and follows this structure:
my-integration/
├── config.json # Integration metadata and action definitions
├── my_integration.py # Main implementation (entry point)
├── __init__.py # Package init — only import and __all__
├── requirements.txt # Dependencies (must include autohive-integrations-sdk)
├── README.md # Documentation
├── icon.png or icon.svg # Integration icon (512×512 pixels)
└── tests/
├── __init__.py # Can be empty
├── context.py # Import setup
└── test_my_integration.py
- Directory name: lowercase with hyphens (
my-integration,google-sheets) - Python module: lowercase with underscores (
my_integration.py) - Action names: snake_case (
list_items,create_record)
mkdir my-integration
cd my-integrationCreate a requirements.txt:
autohive-integrations-sdk~=2.0.0
The ~= (compatible release) operator means >=2.0.0, <2.1.0 — you'll get patch updates with bug fixes, but won't be surprised by minor version changes that could alter SDK behaviour.
Add any additional libraries your integration needs beyond the SDK (e.g., feedparser for RSS parsing, stripe for the Stripe client library).
Install your dependencies:
pip install -r requirements.txtCreate config.json. This file defines your integration's metadata, authentication, and actions.
For public APIs that don't require authentication:
{
"name": "my-integration",
"version": "1.0.0",
"description": "Fetches data from the Example API",
"entry_point": "my_integration.py",
"actions": {
"get_items": {
"display_name": "Get Items",
"description": "Retrieves a list of items from the Example API",
"input_schema": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of items to return (1-100)",
"default": 10,
"minimum": 1,
"maximum": 100
}
},
"required": []
},
"output_schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "List of items"
},
"count": {
"type": "integer",
"description": "Number of items returned"
},
"result": {
"type": "boolean",
"description": "Whether the operation succeeded"
},
"error": {
"type": "string",
"description": "Error message if result is false"
}
}
}
}
}
}| Field | Type | Description |
|---|---|---|
name |
string | Integration name |
version |
string | Semantic version (MAJOR.MINOR.PATCH) |
description |
string | What the integration does |
entry_point |
string | Main Python file name (must end in .py) |
actions |
object | At least one action definition |
| Field | Type | Description |
|---|---|---|
display_name |
string | Human-readable name for the UI (recommended) |
auth |
object | Authentication configuration (see Authentication) |
supports_billing |
boolean | Enable cost tracking via ActionResult.cost_usd (see billing docs) |
supports_connected_account |
boolean | Enable connected account display (see connected account docs) |
Each action in the actions object must have:
| Field | Required | Description |
|---|---|---|
description |
Yes | What the action does |
input_schema |
Yes | JSON Schema defining accepted inputs |
output_schema |
Yes | JSON Schema defining the response structure |
display_name |
Recommended | Human-readable action name for the UI |
The input_schema and output_schema must be valid JSON Schema. The SDK validates inputs and outputs against these schemas at runtime.
For integrations where users provide their own credentials. Use title to label the auth section in the UI:
"auth": {
"type": "custom",
"title": "API Token Authentication",
"fields": {
"type": "object",
"properties": {
"api_token": {
"type": "string",
"format": "password",
"label": "API Token",
"help_text": "Find your API token at https://example.com/settings"
}
},
"required": ["api_token"]
}
}The fields value must be valid JSON Schema. The format, label, and help_text properties are used to render the authentication UI in Autohive.
At runtime, credentials are nested under context.auth["credentials"]. For custom auth, access fields like: context.auth.get("credentials", {}).get("api_token").
For integrations using Autohive's built-in OAuth2 providers:
"auth": {
"type": "platform",
"provider": "github",
"scopes": ["repo", "read:user"]
}With platform auth, the SDK automatically injects the Authorization header into requests made via context.fetch(). You generally don't need to handle tokens yourself.
For public APIs, omit the auth field entirely.
Create my_integration.py:
from autohive_integrations_sdk import (
Integration, ExecutionContext, ActionHandler, ActionResult
)
from typing import Dict, Any
# Load integration configuration
my_integration = Integration.load()
BASE_URL = "https://api.example.com/v1"
@my_integration.action("get_items")
class GetItemsAction(ActionHandler):
"""Retrieves items from the Example API."""
async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> ActionResult:
limit = inputs.get("limit", 10)
try:
response = await context.fetch(
f"{BASE_URL}/items",
method="GET",
params={"limit": limit}
)
items = response.data.get("data", [])
return ActionResult(
data={
"items": items,
"count": len(items),
"result": True,
},
cost_usd=0.0
)
except Exception as e:
return ActionResult(
data={
"items": [],
"count": 0,
"result": False,
"error": str(e),
},
cost_usd=0.0
)Integration.load() — Loads your config.json and creates the integration instance. Call this at module level.
@integration.action("action_name") — Decorator that registers a handler class for the named action. The name must match a key in your config.json actions object.
ActionHandler — Base class for all action handlers. You must implement the async def execute() method.
ExecutionContext — Provided to every handler. Gives you:
context.fetch(url, ...)— Make HTTP requests with automatic auth handling; returns aFetchResponsewith.status,.headers, and.dataattributescontext.auth— Access authentication credentials
ActionResult — The required return type for all action handlers. Contains:
data— Your response data (validated againstoutput_schema)cost_usd— Optional cost in USD for billing tracking (see billing docs)
Use context.fetch() for all HTTP calls. It handles authentication headers, retries, timeouts, and response parsing automatically. It returns a FetchResponse object — access the parsed body via .data, the HTTP status via .status, and response headers via .headers.
# GET with query parameters
response = await context.fetch(
f"{BASE_URL}/items",
method="GET",
params={"limit": 10, "status": "active"}
)
items = response.data # parsed response body
# POST with JSON body
response = await context.fetch(
f"{BASE_URL}/items",
method="POST",
headers={"Content-Type": "application/json"},
json={"name": "New Item", "status": "active"}
)
new_item = response.data
# POST with form-encoded body
response = await context.fetch(
f"{BASE_URL}/items",
method="POST",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={"name": "New Item"}
)
result = response.dataUse inputs.get() with a default for optional fields. Use inputs["key"] for required fields (the SDK validates required fields against your input_schema before calling your handler).
# Optional field — safe access with default
limit = inputs.get("limit", 10)
# Required field — safe because the SDK validates required fields first
customer_id = inputs["customer_id"]
# Optional field — check before using
email = inputs.get("email")
if email:
body["email"] = emailAction handlers normally return an ActionResult whose data is validated against the action's output_schema. When your action encounters an expected error condition (e.g. a resource not found, an API quota exceeded), you can return an ActionError instead. This bypasses output schema validation and returns the error message to the caller:
from autohive_integrations_sdk import ActionError, HTTPError
@my_integration.action("my_action_handler")
class MyActionHandler(ActionHandler):
async def execute(self, inputs: Dict[str, Any], context: ExecutionContext):
try:
response = await context.fetch(url)
except HTTPError as e:
return ActionError(
message=f"API call failed: {e.message}",
cost_usd=0.01 # API call was made, cost was still incurred
)
return ActionResult(data=response.data)Use ActionError for expected, application-level failures. For unexpected infrastructure errors, let exceptions propagate normally.
Use PascalCase with an Action suffix:
@integration.action("list_customers")
class ListCustomersAction(ActionHandler):
...
@integration.action("create_invoice")
class CreateInvoiceAction(ActionHandler):
...Can be empty, or contain a minimal import and export:
# Option 1: empty file (sufficient)
# Option 2: import and export
from .my_integration import my_integration
__all__ = ["my_integration"]Do not add any logic, logging, or additional code to this file.
Add a 512×512 pixel icon for your integration.
Document your integration: what it does, how to authenticate, what each action accepts and returns. See the samples/ directory for a reference README.
Can be empty.
Sets up the import path so tests can find your integration:
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from my_integration import my_integration # noqa: F401import asyncio
from context import my_integration
from autohive_integrations_sdk import ExecutionContext
async def test_get_items():
"""Test fetching items."""
inputs = {"limit": 5}
async with ExecutionContext(auth={}) as context:
result = await my_integration.execute_action("get_items", inputs, context)
# execute_action returns an IntegrationResult
# result.result contains the ActionResult
# result.result.data contains your response data
data = result.result.data
assert "items" in data
assert "count" in data
assert data["result"] is True
print(f"Got {data['count']} items")
if __name__ == "__main__":
asyncio.run(test_get_items())For integrations that require authentication, pass credentials matching your auth.fields schema:
auth = {
"api_token": "your-test-token"
}
async with ExecutionContext(auth=auth) as context:
result = await my_integration.execute_action("get_items", inputs, context)Note: In production, the platform wraps credentials as
{"auth_type": "...", "credentials": {"api_token": "..."}}. That's why production action handlers accesscontext.auth.get("credentials", {}).get("api_token"). When testing locally withexecute_action, the SDK validatescontext.authdirectly against yourauth.fieldsschema, so pass credentials flat.
For platform OAuth integrations (where context.fetch() auto-injects the Bearer token), pass the full wrapped structure in tests:
auth = {
"auth_type": "PlatformOauth2",
"credentials": {"access_token": "your-test-token"}
}
async with ExecutionContext(auth=auth) as context:
result = await my_integration.execute_action("list_repos", inputs, context)For integrations with many actions, split handlers into an actions/ directory:
my-integration/
├── config.json
├── my_integration.py # Loads integration, imports action modules
├── helpers.py # Shared utilities
├── requirements.txt
├── README.md
├── icon.png
├── actions/
│ ├── __init__.py
│ ├── items.py # Item-related actions
│ └── users.py # User-related actions
└── tests/
├── __init__.py
├── context.py
└── test_my_integration.py
Main entry point (my_integration.py):
from autohive_integrations_sdk import Integration
# Explicit path is required for multi-file integrations so that
# config.json is found regardless of which submodule triggers the load
import os
config_path = os.path.join(os.path.dirname(__file__), "config.json")
my_integration = Integration.load(config_path)
# Import action modules to register their handlers
import actionsAction module (actions/items.py):
from my_integration import my_integration
from helpers import get_headers
from autohive_integrations_sdk import ActionHandler, ActionResult, ExecutionContext
from typing import Dict, Any
@my_integration.action("list_items")
class ListItemsAction(ActionHandler):
async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> ActionResult:
...When using this modular pattern, the root __init__.py is optional.
Before submitting, validate your integration using the autohive-integrations-tooling:
# Validate structure and config
python scripts/validate_integration.py my-integration
# Run all code quality checks
python scripts/check_code.py my-integrationSee the tooling repo's documentation for setup instructions and the full list of checks.
- Billing and cost tracking — report per-action costs via
ActionResult.cost_usd - Connected account information — display which account authorized the integration
- autohive-integrations-tooling — CI/CD validation, checklist, and script reference