Intent API
One endpoint. Typed intents. Zero boilerplate.
Intent API is a constraint-driven API framework for Python. Instead of writing dozens of REST endpoints, you declare intents — structured requests that describe what the caller wants to do. The framework dispatches them to the right handler automatically.
POST /api/intent
{
"model": "Todo",
"action": "create",
"payload": { "title": "Ship Intent API", "done": false },
"context": { "type": "user", "team_id": "abc-123" }
}
Install
pip install intent-api
Quickstart
from fastapi import FastAPI
from intent_api import IntentRouter, IntentService, MutationResponse
app = FastAPI()
# 1. Define a service
class TodoService(IntentService):
async def create(self, *, db, user, context, payload):
# Your create logic here
return MutationResponse(success=True, id="1", message="Todo created")
async def list(self, *, db, user, context, skip, limit):
return {"items": [], "total": 0}
# 2. Create router and register services
router = IntentRouter()
router.register("Todo", TodoService())
# 3. Build and include the FastAPI router
app.include_router(router.build(
get_user=my_auth_dependency, # Your auth function
get_db=my_db_dependency, # Your DB session function
))
That's it. One endpoint handles all CRUD + custom commands for every model.
Core Concepts
IntentRequest
Every API call is an IntentRequest:
| Field | Type | Description |
|---|---|---|
model |
str |
Target resource (e.g., "Todo", "User") |
action |
str |
"create", "read", "update", "delete", "list", "custom" |
id |
any? |
Resource ID for read/update/delete |
payload |
dict? |
Data for create/update/custom |
command |
str? |
Named command when action is "custom" |
context |
IntentContext? |
Caller context (role, team, org) |
skip |
int? |
Pagination offset (default: 0) |
limit |
int? |
Pagination limit (default: 10) |
IntentContext
Context tells the backend who is calling and what scope they're in:
{
"type": "member", # Role/surface type
"team_id": "uuid-123", # Team scope
"organization_id": null, # Org scope (optional)
}
IntentService
Subclass IntentService for each model. Override the methods you need:
class UserService(IntentService):
async def create(self, *, db, user, context, payload):
...
async def read(self, *, db, user, id, context):
...
async def update(self, *, db, user, id, context, payload):
...
async def delete(self, *, db, user, id, context):
...
async def list(self, *, db, user, context, skip, limit):
...
async def custom_action(self, *, db, user, context, command, id, payload):
if command == "archive":
return await self._archive(db=db, id=id)
raise ValueError(f"Unknown command: {command}")
Custom Commands
Custom commands let you go beyond CRUD:
{
"model": "Report",
"action": "custom",
"command": "export_csv",
"payload": { "date_from": "2024-01-01", "date_to": "2024-12-31" }
}
Multiple Surfaces
Intent API supports multiple access levels from the same registry:
router = IntentRouter()
router.register("Todo", TodoService())
router.register("User", UserService())
# Standard: authenticated users
app.include_router(router.build(
get_user=my_auth_dependency,
get_db=get_db,
))
# Admin: requires additional authorization
app.include_router(router.build_admin(
get_user=my_auth_dependency,
get_db=get_db,
authorize=lambda user, ctx: user.email.endswith("@mycompany.com"),
))
# Guest: unauthenticated, restricted actions
app.include_router(router.build_guest(
get_db=get_db,
))
Guest Access Control
Mark services as guest-accessible:
class PublicFeedService(IntentService):
is_guest_allowed = True
allowed_guest_actions = ["list", "read"]
async def list(self, *, db, user, context, skip, limit):
# user will be None for guest requests
return {"items": [...], "total": 10}
Auth Integration
Intent API is auth-agnostic. Provide your own get_user dependency:
# Example with Clerk
async def get_user(credentials, context, db):
clerk_user_id = verify_clerk_token(credentials.credentials)
return db.query(User).filter(User.clerk_id == clerk_user_id).first()
# Example with Auth0
async def get_user(credentials, context, db):
payload = decode_auth0_token(credentials.credentials)
return db.query(User).filter(User.auth0_id == payload["sub"]).first()
# Wire it up
app.include_router(router.build(get_user=get_user, get_db=get_db))
Why Intent API?
| Traditional REST | Intent API |
|---|---|
GET /users, POST /users, GET /users/:id, PUT /users/:id, DELETE /users/:id, POST /users/:id/archive, GET /reports/export ... |
POST /api/intent |
| 40+ endpoints to maintain | 1 endpoint, N services |
| Auth middleware on every route | Auth once at the intent surface |
| No standard request format | Every request is an IntentRequest |
| Hard to audit ("which endpoints access sensitive data?") | One place to log all access |
License
Copyright 2026 Chris Bora. All rights reserved.
Free for personal and evaluation use. Commercial use requires a license. See intentapi.dev/pricing for details.
Metadata
Release files for intent-api 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| intent_api-0.1.0.tar.gz | 13.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| intent_api-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.5 kB
Release files / intent_api-0.1.0.tar.gz
| Download URL | intent_api-0.1.0.tar.gz |
|---|---|
| Size | 13.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
79ae0457b9a81c65b9e0a2b89779de58292051c7ca14eb937211a025605c46f8
|
|
BLAKE2b-256 checksum How to use checksums |
c4aec06623f43bf0aa77fc961320d7f6a8afdd9beb3c919bf33c1d69358b24d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Release files / intent_api-0.1.0-py3-none-any.whl
| Download URL | intent_api-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9f190301c31c7affa936632a696d54334fcc58cd50b60dd4ce17e218bdf0aa4a
|
|
BLAKE2b-256 checksum How to use checksums |
9bf4729202b789f0c6dda2adf53d814629c825ac239e756947c4f550b6855a3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|