Create HTTP endpoints quickly using files, without going through a framework.
Project description
Bitpoint
Create HTTP endpoints quickly using files, without going through a framework like Flask or FastAPI.
- File-based routing: the directory structure defines the routes, the file name defines the HTTP method.
- Hot reload: save the file and the server updates automatically. Deploy with a simple
git pull. - Per-endpoint dependencies: each endpoint declares its dependencies with PEP 723 and they are installed automatically in an isolated environment.
Bitpoint is for the moments when a full project feels like too much: exposing a script as a webhook, mocking an API so the frontend can move forward, publishing a small internal service for your team, or keeping a handful of endpoints alive on a personal server. In all these cases the API is not the product, it is just plumbing, and spending an afternoon on scaffolding, virtual environments and deploy pipelines is out of proportion. With Bitpoint you write one file and you get one endpoint.
Under the hood, Bitpoint stays out of your way. There is nothing to register and nothing to configure: no app object, no decorators, no config files, just a handle function per file. Dependency isolation is delegated to uv, with a cached environment per endpoint, so two endpoints can use incompatible versions of the same library without ever conflicting. Responses follow the type you return: a string becomes plain text, a dict becomes JSON, a tuple adds a status code and headers, and the sensible thing happens by default.
Installation
pip install bitpoint
Or, if you use uv:
uv tool install bitpoint
Python 3.9 or later. If you want per-endpoint dependencies, uv must be available on the server.
Quickstart
Create a directory called routes and, inside it, a subdirectory called hello (this will be the route). Inside hello, create a file called get.py with the following content:
# routes/hello/get.py
def handle(request):
return "world"
Start the server with:
python -m bitpoint
You can now try your endpoint:
curl localhost:8000/hello
It will return:
world
Routing
The URL path maps to the directory path inside routes, and the HTTP method maps to the file name:
routes/
├── hello/
│ └── get.py → GET /hello
├── users/
│ ├── get.py → GET /users
│ ├── post.py → POST /users
│ └── [id]/
│ ├── get.py → GET /users/42
│ └── delete.py → DELETE /users/42
└── lib/ → shared code, does not generate routes
└── db.py
Valid file names are the HTTP methods in lowercase: get.py, post.py, put.py, patch.py, delete.py, head.py and options.py. Any other file is ignored and does not generate routes, so you can keep helper modules next to your endpoints.
Dynamic routes
A directory whose name is wrapped in brackets captures a URL segment:
# routes/users/[id]/get.py
def handle(request):
return {"user_id": request.params["id"]}
curl localhost:8000/users/42
# {"user_id": "42"}
Captured values always arrive as str. Type conversion is the endpoint's responsibility.
The handle contract
Each endpoint exposes a handle(request) function. It is the only symbol Bitpoint looks for in the file, the rest of the module is yours.
The request object
| Attribute | Type | Description |
|---|---|---|
request.method |
str |
HTTP method in uppercase, for example "GET" |
request.path |
str |
Request path, for example /users/42 |
request.params |
dict[str, str] |
Dynamic path segments |
request.args |
dict[str, str] |
Query string parameters |
request.headers |
dict[str, str] |
Headers, case-insensitive keys |
request.body |
bytes |
Raw request body |
request.json() |
Any |
Body parsed as JSON, raises a 400 error if invalid |
Example with a query string:
# routes/greet/get.py
def handle(request):
name = request.args.get("name", "world")
return f"Hello, {name}!"
curl "localhost:8000/greet?name=Bob"
# Hello, Bob!
Return values
The return type determines the response:
| Return | Response |
|---|---|
str |
200, text/plain; charset=utf-8 |
dict or list |
200, application/json |
bytes |
200, application/octet-stream |
(body, status) |
As above, with the given status code |
(body, status, headers) |
Additionally with custom headers |
None |
204 No Content |
# routes/users/post.py
def handle(request):
data = request.json()
return {"created": data["name"]}, 201
Errors
- If
handleraises an exception, Bitpoint responds with500. - In development mode (the default) the body includes the traceback. In production (
--production) the body is a generic message and the traceback goes to the log. - Path with no matching directory:
404. Directory exists but there is no file for that method:405with theAllowheader listing the available methods.
Dependencies
Declare each endpoint's dependencies with a PEP 723 block at the top of the file, the same format understood by uv and pipx:
# routes/status/get.py
# /// script
# dependencies = ["requests"]
# ///
def handle(request):
import requests
return requests.get("https://example.com").json()
Bitpoint delegates resolution and installation to uv: each endpoint runs with its dependencies in an isolated, cached environment. Two endpoints can use incompatible versions of the same library without conflict.
- An endpoint without a PEP 723 block runs in the base environment, with no extra cost.
- Installation happens on first startup and whenever the dependency block changes, not on every request.
Security note: installing dependencies automatically after a
git pullmeans running third-party code at deploy time. If you prefer to control that step, start with--no-installand Bitpoint will fail with a clear error on endpoints whose dependencies are not already installed.
Shared code
The routes/lib/ directory is reserved: it does not generate routes and is importable from any endpoint:
# routes/lib/db.py
def get_connection():
...
# routes/users/get.py
from lib.db import get_connection
def handle(request):
conn = get_connection()
...
Configuration
Everything is controlled from the command line, there is no configuration file:
python -m bitpoint [options]
| Option | Default | Description |
|---|---|---|
--port |
8000 |
Listening port |
--host |
127.0.0.1 |
Listening address |
--dir |
./routes |
Root directory for endpoints |
--production |
disabled | Hides tracebacks and disables filesystem-based hot reload |
--no-install |
disabled | Does not install dependencies automatically |
Hot reload
In development mode, Bitpoint watches the routes directory and reloads each module when its file changes. There is no shared state across reloads: if your endpoint keeps in-memory state (caches, connections), it is lost on reload. For persistent state use external resources (a database, Redis, files).
In production, filesystem-based reload is disabled. The recommended deploy flow is git pull followed by a reload signal:
git pull && kill -HUP $(cat bitpoint.pid)
A real example
A GitHub webhook that sends a Telegram message on every push:
# routes/webhooks/github/post.py
# /// script
# dependencies = ["requests"]
# ///
import os
import requests
def handle(request):
if request.headers.get("x-github-event") != "push":
return None
payload = request.json()
pusher = payload["pusher"]["name"]
repo = payload["repository"]["name"]
commits = len(payload["commits"])
requests.post(
f"https://api.telegram.org/bot{os.environ['TELEGRAM_TOKEN']}/sendMessage",
json={
"chat_id": os.environ["TELEGRAM_CHAT_ID"],
"text": f"{pusher} pushed {commits} commit(s) to {repo}",
},
)
return None
One file, deployed with a git pull. No project, no virtualenv, no route registration, no restart.
Why not Flask or FastAPI?
Use Flask or FastAPI when the API is your product: they are excellent frameworks and Bitpoint does not try to compete with them.
Bitpoint is for when the API is just plumbing. A webhook here, an internal endpoint there, a mock for the frontend. In those cases a framework gives you power you will not use at the price of ceremony you have to pay every time: a project to scaffold, an app to configure, routes to register, a virtualenv to maintain and a server to restart. Bitpoint removes all of that by turning the decisions into conventions: the filesystem is the router, the file is the endpoint, and its header declares its dependencies.
If an endpoint outgrows Bitpoint, nothing locks you in: handle is a plain function, and moving it to a Flask or FastAPI route is a copy-paste.
Small core
Some areas are explicitly not covered in order to keep the core small:
- Middleware and global hooks
- Authentication
- WebSockets and streaming
asyncendpoints- Static files
If you need any of this today, Bitpoint is not your tool, you will have to use external tools.
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 bitpoint-0.1.0.tar.gz.
File metadata
- Download URL: bitpoint-0.1.0.tar.gz
- Upload date:
- Size: 16.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9f71960dfd661f335b3524536deecb548cf9cbccda6c9a34e1b1e91d2ea45a9
|
|
| MD5 |
1176fc55920794dba410a2b355c6aac6
|
|
| BLAKE2b-256 |
959428fc0332834b73c74cff5f18635ad922b4fa6280b769eceaa4c356a78c84
|
File details
Details for the file bitpoint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: bitpoint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b8b921e1c2126e7a5ea3a9bb84c2c8af45a0c96f2d67711b24325c3954f8a72
|
|
| MD5 |
d8bb4e7f0cc29160db5825e433b6469d
|
|
| BLAKE2b-256 |
346cf3b0cc26bec3136f9d9fd8b9da4920250856b8effcb5e095338df1759e40
|