hai-agent-api
Shared FastAPI surface for the agents API. This package defines the HTTP routes, request/response models, and authentication dependencies. Your application supplies the backing logic by implementing the service protocols and passing them to create_router.
Install
uv pip install hai-agent-api
You also need an ASGI server to run locally, for example:
uv pip install "uvicorn[standard]"
Quick start
hai-agent-api does not ship a runnable server. Wire the router into your own FastAPI app and provide implementations for all five services.
# app.py
from agent_api import ApiConfig, AuthConfig, Services, create_router, install_exception_handlers
from agent_api.user import ApiUser
from agent_interface.specs.skill import Skill
from agp_types import Page, PageRequest
from fastapi import FastAPI, HTTPException
# ---------------------------------------------------------------------------
# Example service implementations
# ---------------------------------------------------------------------------
class InMemorySkillService:
"""Minimal skill catalog backed by an in-memory dict."""
def __init__(self) -> None:
self._skills: dict[str, Skill] = {}
async def create_skill(self, user: ApiUser, create: Skill) -> Skill:
if create.name in self._skills:
raise HTTPException(status_code=409, detail=f"Skill {create.name!r} already exists.")
self._skills[create.name] = create
return create
async def get_page(
self,
user: ApiUser,
page_request: PageRequest,
*,
name: str | None = None,
search: str | None = None,
) -> Page[Skill]:
items = list(self._skills.values())
if name:
items = [s for s in items if name.lower() in s.name.lower()]
if search:
q = search.lower()
items = [s for s in items if q in s.name.lower() or q in (s.description or "").lower()]
return Page[Skill](items=items, page=page_request.page, size=page_request.size, total=len(items))
async def get_skill(self, user: ApiUser, name: str) -> Skill:
skill = self._skills.get(name)
if skill is None:
raise HTTPException(status_code=404, detail=f"Skill {name!r} not found.")
return skill
async def update_skill(self, user: ApiUser, name: str, update: Skill) -> Skill:
if update.name != name:
raise HTTPException(status_code=422, detail="update.name must match the path name.")
if name not in self._skills:
raise HTTPException(status_code=404, detail=f"Skill {name!r} not found.")
self._skills[name] = update
return update
async def delete_skill(self, user: ApiUser, name: str) -> None:
if name not in self._skills:
raise HTTPException(status_code=404, detail=f"Skill {name!r} not found.")
del self._skills[name]
class StubService:
"""Placeholder for services you have not implemented yet."""
def __getattr__(self, name: str):
async def _not_implemented(*_args, **_kwargs):
raise HTTPException(status_code=501, detail=f"{name} is not implemented.")
return _not_implemented
# ---------------------------------------------------------------------------
# FastAPI app
# ---------------------------------------------------------------------------
app = FastAPI(title="agents-api", version="0.0.0")
services = Services(
agents=StubService(),
environments=StubService(),
skills=InMemorySkillService(),
sessions=StubService(),
memories=StubService(),
webhooks=StubService(),
)
app.include_router(
create_router(
services,
config=ApiConfig(auth=AuthConfig(mode="local")),
),
prefix="/api",
)
install_exception_handlers(app)
Run the server:
uvicorn app:app --reload --port 8000
Open the interactive docs at http://localhost:8000/docs. Skill endpoints are available under /api/v2/skills; unimplemented services return 501 Not Implemented.
Try a request
curl -s -X POST http://localhost:8000/api/v2/skills \
-H 'Content-Type: application/json' \
-d '{"name": "my-skill", "description": "Example", "body": "Do the thing."}' | jq
Service protocols
Implement these protocols (structural typing—no base class required) and pass them in a Services container:
| Protocol | Router prefix | Responsibility |
|---|---|---|
AgentServiceProtocol |
/v2/agents |
Agent catalog CRUD and spec resolution |
EnvironmentServiceProtocol |
/v2/environments |
Environment catalog CRUD |
SkillServiceProtocol |
/v2/skills |
Skill catalog CRUD |
SessionServiceProtocol |
/v2/sessions |
Agentic session lifecycle, events, quota |
MemoryServiceProtocol |
/v2/memories |
Per-org key/value memory (hidden from OpenAPI) |
WebhookServiceProtocol |
/v2/webhooks |
Webhook subscription CRUD and delivery history |
ScheduleServiceProtocol |
/v2/schedules |
Cron schedules that create sessions; optional — the tree mounts only when wired (soft launch, hidden from OpenAPI) |
Full method signatures live in agent_api.services.protocols. Raise fastapi.HTTPException for expected client errors (404, 409, 422, etc.). Domain exceptions such as agent_api.exceptions.IdempotencyKeyConflict are mapped to the standard error body when you call install_exception_handlers(app) on your FastAPI app (Agent Platform does this in hplatform.app).
Error responses
All API errors use a single JSON shape:
{
"message": "Human-readable summary",
"detail": ["..."]
}
messageis a short summary string.detailis always an array. For validation failures it contains the usual Pydantic error objects; for application errors it contains objects withtypeandmessage.
Pydantic request validation, HTTPException, and registered domain exceptions are all normalized to this format by install_exception_handlers.
Configuration
ApiConfig controls router behaviour:
| Field | Default | Purpose |
|---|---|---|
long_poll_max_wait_for_seconds |
60 |
Upper bound for long-poll on /sessions/{id}/changes |
session_share_url_template |
/share/api/v1/trajectories/{session_id} |
Template for public share links |
auth |
AuthConfig(mode="platform") |
How requests are authenticated (see below) |
Authentication
Pass AuthConfig via ApiConfig.auth when calling create_router:
| Mode | Use case | Behaviour |
|---|---|---|
platform |
Agent Platform (default) | Requires X-User-Sub and X-User-Org headers injected by the gateway; returns 401 when missing |
local |
Local development / standalone apps | Protected routes accept optional identity headers and fall back to default UUIDs when omitted |
Local mode defaults:
| Field | Default |
|---|---|
default_user_id |
00000000-0000-0000-0000-000000000001 |
default_org_id |
00000000-0000-0000-0000-000000000002 |
default_email |
local-dev@example.com |
Override headers when you need a specific identity:
curl -s http://localhost:8000/api/v2/skills \
-H 'X-User-Sub: 11111111-1111-1111-1111-111111111111' \
-H 'X-User-Org: 22222222-2222-2222-2222-222222222222'
Agent Platform keeps the default platform mode—no change required in production wiring.
API layout
create_router returns a router with this structure:
/v2/sessions/...
/v2/memories/...
/v2/skills/...
/v2/environments/...
/v2/agents/...
/v2/webhooks/...
/v2/schedules/... (only when Services.schedules is provided)
Mount it under your chosen prefix (commonly /api, yielding /api/v2/...).
Metadata
Release files for hai-agent-api 0.1.90
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hai_agent_api-0.1.90-py3-none-any.whl | Python 3 | none | any | Details |
Release files / hai_agent_api-0.1.90-py3-none-any.whl
| Download URL | hai_agent_api-0.1.90-py3-none-any.whl |
|---|---|
| Size | 79.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
60f819eeb37d95f8e17c9dc4aadd1a60fa342c4e91ed1a1e0fc0acaa9b68d07d
|
|
BLAKE2b-256 checksum How to use checksums |
ed6911f890997479d2455ea78c4bafb428110d4701bc0d74bed365defa793575
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.
Transparency log