Intent API
One endpoint. Typed intents. Install from PyPI and npm — no public source repo required.
This documentation mirrors the shipped packages intent-api (PyPI) and @intent-api/react (npm). Framework source is not published as a public repository; install from the registries above.
Overview
Intent API is a constraint-driven API framework: instead of dozens of REST endpoints, every call is a typed intent — one POST body describing model, action, payload, and context. The backend dispatches to your services.
The frontend uses two hooks — useIntentQuery and useIntentMutation — backed by TanStack Query. A streaming hook (useIntentStream) handles SSE for AI chat and long-running operations.
A built-in MCP server exposes your entire service catalog to Claude Desktop, Cursor, and any MCP-compatible AI host — one call to build_mcp().
Need a full app scaffold? See paid templates.
Install
Backend (Python):
pip install intent-apiWith OpenTelemetry export (optional):
pip install 'intent-api[otel]'Frontend (React):
npm install @intent-api/react
# peer: react, @tanstack/react-query, axiosAI Harness (optional — adds agent loop and chat UI):
pip install intent-api-harness
npm install @intent-api/react-chatThe intent protocol
Typical user-facing endpoint: POST /api/intent
{
"model": "Todo",
"action": "create",
"payload": { "title": "Ship Intent API", "done": false },
"context": { "type": "user", "team_id": "abc-123" }
}IntentRequest fields
| Field | Description |
|---|---|
| model | Resource name, e.g. "Todo", "User" |
| action | "create" | "read" | "update" | "delete" | "list" | "custom" |
| id | Resource id for read/update/delete |
| payload | Data for create/update/custom |
| command | Named command when action is "custom" |
| context | Caller scope (role, team, org) |
| skip / limit | Pagination for list |
Python (FastAPI)
Subclass IntentService, register with IntentRouter, then include_router(router.build(...)).
from fastapi import FastAPI
from intent_api import IntentRouter, IntentService, MutationResponse
app = FastAPI()
class TodoService(IntentService):
async def create(self, *, db, user, context, payload):
return MutationResponse(success=True, id="1", message="Todo created")
async def list(self, *, db, user, context, skip, limit):
return {"items": [], "total": 0}
router = IntentRouter()
router.register("Todo", TodoService())
app.include_router(router.build(
get_user=my_auth_dependency,
get_db=my_db_dependency,
))Auth is your choice: pass a get_user dependency (e.g. Clerk JWT verification). Guest and machine surfaces use additional router builders — see Multiple surfaces.
Custom commands
Use @custom_action to add named commands beyond standard CRUD. Decorated methods are auto-collected and dispatched automatically.
from intent_api import IntentService, custom_action
class BlogPostService(IntentService):
@custom_action()
async def generate(self, *, db, user, context, id, payload):
# POST /api/intent { "model": "BlogPost", "action": "custom", "command": "generate" }
return {"generated": True}
@custom_action(name="export_mdx")
async def export(self, *, db, user, context, id, payload):
return {"exported": True}Governance (optional)
Add a runtime to enforce permissions, billing checks, quotas, and audit logging on every intent — before your handler runs. Start with zero-config dev mode and swap in production providers when ready.
from intent_api.runtime import IntentRuntime
runtime = IntentRuntime.dev_mode(
role_permissions={"admin": ["*"], "member": ["todo:list", "todo:create"]},
feature_plans={"exports": ["pro"]},
quota_limits={"exports": 100},
)
router = IntentRouter(runtime=runtime)
router.register("Todo", TodoService())React
Wrap the app with IntentApiProvider (base URL + token callback + default context). Use hooks in components.
import { IntentApiProvider, useIntentQuery, useIntentMutation } from "@intent-api/react";
<IntentApiProvider
baseURL="https://api.myapp.com"
getToken={async () => (await getToken()) ?? null}
defaultContext={{ type: "user" }}
>
<App />
</IntentApiProvider>
const { data } = useIntentQuery("Todo", "list", { skip: 0, limit: 20 });
const createTodo = useIntentMutation("Todo", "create");
createTodo.mutate({ payload: { title: "Hi" } });Query keys follow ["intent", model, ...] so you can invalidate after mutations.
Streaming
useIntentStream handles SSE for AI chat and long-running operations. Call stream() to start, abort() to cancel.
import { useIntentStream } from "@intent-api/react";
const { stream, abort, isStreaming } = useIntentStream(
"Chat",
"stream",
{
onEvent: (event) => { /* handle typed SSE event */ },
onEnd: () => { /* stream finished */ },
onError: (msg) => { /* handle error */ },
}
);
// Trigger a stream
stream({ payload: { message: "Hello", session_id: "abc" } });Multiple surfaces
Intent API has five surfaces, each with its own endpoint prefix and auth rules:
| Surface | Endpoint | Use case |
|---|---|---|
| standard | POST /api/intent | Authenticated dashboard users |
| admin | POST /api/admin-intent | Internal tooling |
| guest | POST /api/guest-intent | Public endpoints, no auth |
| machine | POST /api/machine-intent | API keys, SDKs, M2M |
| mcp | POST /mcp/ (mounted) | AI agents via MCP protocol |
Backend builder methods:
app.include_router(router.build(get_user=get_current_user, get_db=get_db))
app.include_router(router.build_admin(get_user=get_admin_user, get_db=get_db))
app.include_router(router.build_guest(get_db=get_db))
app.include_router(router.build_machine(get_user=get_machine_user, get_db=get_db))
# MCP — see the MCP Server section belowReact: matching hooks per surface.
useIntentQuery("Todo", "list");
useAdminIntentQuery("User", "list");
useGuestIntentQuery("Post", "list");
useMachineIntentMutation("Event", "ingest_batch");MCP Server
Intent API ships a built-in MCP server. One call to build_mcp() exposes your entire registered service catalog to Claude Desktop, Cursor, and any MCP-compatible AI host. AI agents discover and call your real business intents through the same auth and policy pipeline as every other request.
from fastapi import FastAPI
from intent_api import IntentRouter
from intent_api.mcp_auth import bearer_token_auth
router = IntentRouter()
router.register("Brand", BrandService())
router.register("BlogPost", BlogPostService())
router.register("Internal", InternalService(), expose_mcp=False) # hide from MCP
# REST surfaces
app.include_router(router.build(get_user=get_current_user, get_db=get_db))
# MCP surface — build once, mount on app
mcp_app = router.build_mcp(
get_user=bearer_token_auth(verify_fn=verify_token, resolve_user=get_user_from_token),
get_db=get_db,
)
app = FastAPI(lifespan=mcp_app.lifespan) # required
app.mount("/mcp", mcp_app)AI agents can read two discovery resources before calling your services:
| Resource | Returns |
|---|---|
| intent://models | Lightweight list of all visible models |
| intent://schema | Full catalog with actions, commands, and payload schemas |
Use expose_mcp=False on router.register() to hide any service from the MCP surface while keeping it accessible on REST.
AI Harness
intent-api-harness adds an AI agent loop to any Intent API app in 3 lines. Register a ChatService and the AI can discover and call your real business intents through the same auth, quota, and audit pipeline as every other request.
pip install intent-api-harness
npm install @intent-api/react-chatBackend — register ChatService:
from intent_api_harness import ChatService, LiteLLMProvider, ChatEngineConfig
provider = LiteLLMProvider(model="openai/gpt-4o")
router.register("Chat", ChatService(
model_provider=provider,
config=ChatEngineConfig(
app_name="My App",
custom_instructions="You help users manage their content strategy.",
),
))Frontend — drop in <IntentChat />. Zero config when wrapped in IntentApiProvider:
import { IntentChat } from "@intent-api/react-chat";
// Reads baseURL and auth token from the existing IntentApiProvider
<IntentChat placeholder="Ask about your content strategy..." showToolLog />Swap the model in one line: LiteLLMProvider(model="anthropic/claude-sonnet-4-20250514"). Any LiteLLM-supported provider works.
Structured Logging
One call adds structured logging with automatic context on every intent: request ID, model, action, actor, and team. Logs are emitted to the console by default. Set otel_endpoint to ship to any OTLP collector (Honeycomb, Datadog, Grafana, and others).
from fastapi import FastAPI
from intent_api import IntentLogConfig, setup_intent_logging
app = FastAPI()
handle = setup_intent_logging(
IntentLogConfig(
service_name="my-app-api",
service_version="1.0.0",
otel_endpoint="https://ingest-1.sherlocklabs.dev/v1/logs", # optional
otel_headers={"Authorization": "Bearer YOUR_SHERLOCK_KEY"}, # optional
),
app=app,
)Requires pip install intent-api[otel] when otel_endpoint is set. Without it, console output only, no external network calls. Any OTLP/HTTP collector works: Sherlock, Honeycomb, Datadog, Grafana, and others. Change otel_endpoint and otel_headers to switch collectors with no code change.
DevTools
With debug=True on the router, the backend exposes a registry for click-to-source. On the client, wrap with IntentDevToolsProvider and render IntentDevTools in development to inspect every intent on the page and open the backend handler in Cursor or VS Code at the exact line.
// main.tsx — development only
{IS_DEV && <IntentDevTools apiBaseUrl={API_BASE_URL} editor="cursor" />}License
Free for commercial use under the Intent API License (IACL): no competing framework or hosted substitute. Full text ships with the PyPI and npm packages.