Vanty App: multi-tenant CRM (people, companies, activities, feedback).
Project description
vanty-crm
Lean, multi-tenant CRM toolkit for FastAPI + Tortoise ORM. Part of the Vanty ecosystem.
- People (leads/contacts/customers), companies, activities, feedback
- Tortoise models on top of
OrganizationScopedModelfor tenant isolation - FastAPI routers (public + admin) with cursor pagination
- Domain events on the shared
vanty-coreevent bus — subscribe from any package to react to CRM lifecycle moments - CSV import (preview + apply)
CRM Person is an external contact, never a platform user. An optional
linked_user_id records the user account a Person was converted into.
Install
uv add vanty-crm
Quickstart
from fastapi import FastAPI
from vanty_crm import CrmApp, CrmSettings, mount_crm_router
app = FastAPI()
kit = mount_crm_router(app, CrmSettings(database_url="sqlite://./crm.db"))
@app.on_event("startup")
async def _startup() -> None:
await kit.init_orm(generate_schemas=True)
@app.on_event("shutdown")
async def _shutdown() -> None:
await kit.close_orm()
# Optional: admin router (mount behind your own RBAC)
from vanty_crm.fastapi.dependencies import require_superuser
def my_superuser_check() -> None:
... # raise HTTPException if caller is not a superuser
app.dependency_overrides[require_superuser] = my_superuser_check
app.include_router(kit.admin_router)
Subscribe to vanty_auth.user.signed_up
The headline cross-package recipe — when a new user signs up via vanty-auth,
upsert a CRM Person for them so marketing/CS workflows can pick them up:
from vanty_auth.events import UserSignedUp
from vanty_core.events import on
from vanty_crm.services.person_service import PersonService
person_service = PersonService()
@on(UserSignedUp)
async def upsert_person_on_signup(evt: UserSignedUp) -> None:
if evt.organization_id is None:
return
await person_service.upsert_by_email(
organization_id=evt.organization_id,
email=evt.email,
defaults={"linked_user_id": evt.user_id, "source": evt.source or "signup"},
)
That handler is the contract proven by tests/test_signup_subscriber.py.
Public API
Mounted at /crm by default. All routes (except POST /crm/feedback) require
the X-Organization-ID header (or an active vanty_auth AuthContext).
| Method | Path | Notes |
|---|---|---|
| GET | /crm/people |
Filter by search, owner_id, stage, company_id. Cursor pagination via cursor/limit. |
| POST | /crm/people |
Create person |
| GET | /crm/people/{id} |
Get person |
| PATCH | /crm/people/{id} |
Partial update |
| DELETE | /crm/people/{id} |
Hard delete |
| POST | /crm/people/merge |
Merge two people |
| GET | /crm/people/{id}/activities |
Activity timeline |
| POST | /crm/people/{id}/activities |
Log a note/email/call/event |
| GET | /crm/companies |
Filter by search, owner_id |
| POST | /crm/companies |
Create company |
| GET | /crm/companies/{id} |
|
| PATCH | /crm/companies/{id} |
|
| DELETE | /crm/companies/{id} |
|
| POST | /crm/companies/{id}/people |
Attach person |
| DELETE | /crm/companies/{id}/people/{person_id} |
Detach person |
| POST | /crm/feedback |
Public — accepts unauthenticated submissions |
| GET | /crm/feedback |
List (auth required) |
| PATCH | /crm/feedback/{id} |
Update / mark resolved |
Pagination uses an opaque cursor (the last id returned). Responses include
next_cursor while more rows are available.
Admin API
Mounted by host at /admin/crm behind require_superuser:
GET /admin/crm/people— cross-organization list (filter byorganization_id)GET /admin/crm/companiesGET /admin/crm/feedbackGET /admin/crm/activitiesPOST /admin/crm/imports/people— upload a CSV file, returns previewPOST /admin/crm/imports/people/{import_id}/apply— apply a previewed import
CSV imports are held in process memory between preview and apply (v1).
Events
Every event is a frozen dataclass registered with @event(...):
| Event name | Class |
|---|---|
vanty_crm.person.created |
PersonCreated |
vanty_crm.person.updated |
PersonUpdated |
vanty_crm.person.merged |
PersonMerged |
vanty_crm.person.deleted |
PersonDeleted |
vanty_crm.company.created |
CompanyCreated |
vanty_crm.company.updated |
CompanyUpdated |
vanty_crm.feedback.received |
FeedbackReceived |
vanty_crm.feedback.resolved |
FeedbackResolved |
vanty_crm.activity.logged |
ActivityLogged |
Develop
uv sync
uv run pytest --no-cov -p no:cacheprovider
Publish to its own GitHub repo
This package lives inside the Vanty monorepo but ships as a standalone
PyPI package + GitHub repo. The publish-github Make target uses
git-filter-repo to extract
the vanty-crm/ subdirectory with full history and push it to a fresh repo:
brew install git-filter-repo # one-time
make publish-github REPO=git@github.com:advantch/vanty-crm.git
License
MIT — © 2026 Themba themba@advantch.com
Project details
Release history Release notifications | RSS feed
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 vanty_crm-0.3.0.tar.gz.
File metadata
- Download URL: vanty_crm-0.3.0.tar.gz
- Upload date:
- Size: 106.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e4f5d0e58d0e8fa38c659b07bfff8f185221b081ec211cd46c292d3c52e231a
|
|
| MD5 |
7ec6bc58dbcbaea956de58097b551e8e
|
|
| BLAKE2b-256 |
39f888f0d6125241f2f7b3bc62ff482546f4860bdf7eff46aff24b80f731b356
|
Provenance
The following attestation bundles were made for vanty_crm-0.3.0.tar.gz:
Publisher:
release.yml on advantch/vanty-crm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vanty_crm-0.3.0.tar.gz -
Subject digest:
4e4f5d0e58d0e8fa38c659b07bfff8f185221b081ec211cd46c292d3c52e231a - Sigstore transparency entry: 1409631098
- Sigstore integration time:
-
Permalink:
advantch/vanty-crm@915065384ef6b23af112b4ff32fbc5d5b6b6810a -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/advantch
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@915065384ef6b23af112b4ff32fbc5d5b6b6810a -
Trigger Event:
push
-
Statement type:
File details
Details for the file vanty_crm-0.3.0-py3-none-any.whl.
File metadata
- Download URL: vanty_crm-0.3.0-py3-none-any.whl
- Upload date:
- Size: 25.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94bbb9bfa8f6d51474d7002f53c6dc0d1defdf5cd59e1da1627d286de0575055
|
|
| MD5 |
cdec160922590a572edfb3af9e564ea3
|
|
| BLAKE2b-256 |
b1b7be7ac6566ccf862d04546880034c779506bcbf07ac6404f25311963bec4c
|
Provenance
The following attestation bundles were made for vanty_crm-0.3.0-py3-none-any.whl:
Publisher:
release.yml on advantch/vanty-crm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vanty_crm-0.3.0-py3-none-any.whl -
Subject digest:
94bbb9bfa8f6d51474d7002f53c6dc0d1defdf5cd59e1da1627d286de0575055 - Sigstore transparency entry: 1409631128
- Sigstore integration time:
-
Permalink:
advantch/vanty-crm@915065384ef6b23af112b4ff32fbc5d5b6b6810a -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/advantch
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@915065384ef6b23af112b4ff32fbc5d5b6b6810a -
Trigger Event:
push
-
Statement type: