New

MCP server ships built-in with every Intent API app·Read the docs →

Documentation

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-api

With OpenTelemetry export (optional):

pip install 'intent-api[otel]'

Frontend (React):

npm install @intent-api/react
# peer: react, @tanstack/react-query, axios

AI Harness (optional — adds agent loop and chat UI):

pip install intent-api-harness
npm install @intent-api/react-chat

The 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

FieldDescription
modelResource name, e.g. "Todo", "User"
action"create" | "read" | "update" | "delete" | "list" | "custom"
idResource id for read/update/delete
payloadData for create/update/custom
commandNamed command when action is "custom"
contextCaller scope (role, team, org)
skip / limitPagination 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:

SurfaceEndpointUse case
standardPOST /api/intentAuthenticated dashboard users
adminPOST /api/admin-intentInternal tooling
guestPOST /api/guest-intentPublic endpoints, no auth
machinePOST /api/machine-intentAPI keys, SDKs, M2M
mcpPOST /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 below

React: 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:

ResourceReturns
intent://modelsLightweight list of all visible models
intent://schemaFull 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-chat

Backend — 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.