A constraint-driven API framework for Python. One endpoint. Typed intents. Zero boilerplate.
Project description
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file intent_api-0.1.0.tar.gz.
File metadata
- Download URL: intent_api-0.1.0.tar.gz
- Upload date:
- Size: 13.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79ae0457b9a81c65b9e0a2b89779de58292051c7ca14eb937211a025605c46f8
|
|
| MD5 |
21eea93b7769fac66e10f9dbf2e66c4a
|
|
| BLAKE2b-256 |
c4aec06623f43bf0aa77fc961320d7f6a8afdd9beb3c919bf33c1d69358b24d1
|
File details
Details for the file intent_api-0.1.0-py3-none-any.whl.
File metadata
- Download URL: intent_api-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f190301c31c7affa936632a696d54334fcc58cd50b60dd4ce17e218bdf0aa4a
|
|
| MD5 |
b190a64e9816ce899375a8b126ee2982
|
|
| BLAKE2b-256 |
9bf4729202b789f0c6dda2adf53d814629c825ac239e756947c4f550b6855a3b
|