Temporary self-destructing message API built on Cloudflare Workers with Python. Messages are stored in Cloudflare KV and automatically destroyed after being read or after 24 hours — whichever comes first.
Stack: Python Worker + Cloudflare KV
The API runs as a Cloudflare Worker using the Python runtime. Messages are persisted in Cloudflare KV, a global low-latency key-value store, with a 24-hour TTL.
1. POST / → Generate unique ID → Store message in KV (TTL 24h) → Return { id }
2. GET /{id} → Fetch message from KV → Return { message } → Delete from KV
3. (after 24h) → KV automatically expires the key
├── src/
│ ├── entry.py # Worker entrypoint, routing, CORS and rate limiting
│ ├── handlers.py # Request handlers for POST / and GET /{id}
│ ├── rate_limiter.py # Sliding-window per-IP rate limiter
│ └── utils.py # Helpers: ID generation, JSON responses, validation
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── test_cors_errors.py
│ ├── test_get.py
│ ├── test_post.py
│ └── test_rate_limiter.py
├── .github/
│ └── workflows/
│ └── deploy.yml # CI/CD: test + deploy on push to main
├── wrangler.toml # Cloudflare Workers configuration
├── pyproject.toml # Project metadata and dev dependencies
└── .env.example # Environment variable template
| Field | Details |
|---|---|
| Request body | {"message": "your secret text"} |
| Response | {"id": "abc12"} |
| Content-Type | application/json |
| Status | Description |
|---|---|
200 |
Message created successfully |
400 |
Invalid JSON or missing/empty message field |
413 |
Message exceeds 100 KB |
429 |
Rate limit exceeded |
500 |
Could not generate a unique ID |
The message is deleted immediately after being read.
| Field | Details |
|---|---|
| Path parameter | id — the 5-character message identifier |
| Response | {"message": "your secret text"} |
| Status | Description |
|---|---|
200 |
Message returned (and destroyed) |
404 |
Message not found or already read |
429 |
Rate limit exceeded |
- pyenv (Python version management)
- uv (Python package manager)
- A Cloudflare account
-
Clone the repository
git clone https://github.com/your-org/crypt-api-worker.git cd crypt-api-worker -
Create your environment file
cp .env.example .env # Fill in CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN -
Install dependencies
uv sync
-
Create a KV namespace in Cloudflare
Go to the Cloudflare dashboard → Workers & Pages → KV → Create a namespace named
MESSAGES. Copy the namespace ID and updatewrangler.toml:[[kv_namespaces]] binding = "MESSAGES" id = "your-namespace-id-here"
-
Run locally
uv run pywrangler dev
curl -X POST https://your-worker.workers.dev/ \
-H "Content-Type: application/json" \
-d '{"message": "This is a secret message"}'Response:
{ "id": "abc12" }curl https://your-worker.workers.dev/abc12Response:
{ "message": "This is a secret message" }Requesting the same ID again returns:
{ "error": "Message not found" }Run the full test suite with coverage:
uv run pytest --cov=src --cov-report=term-missingMinimum required coverage: 80% (enforced by pyproject.toml and CI).
uv run pywrangler deployRequires CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN environment variables (set in .env or exported).
Automatic deployment on every push to main via GitHub Actions (.github/workflows/deploy.yml).
Required GitHub Secrets:
| Secret | Description |
|---|---|
CF_ACCOUNT_ID |
Cloudflare Account ID (found in dashboard) |
CF_API_TOKEN |
Cloudflare API Token with Workers permissions |
- Free tier quotas: 100,000 reads/day, 1,000 writes/day, 1,000 deletes/day per KV namespace.
- Rate limiter is per-isolate: Each Cloudflare Worker isolate maintains its own in-memory rate limiter. This is not globally consistent across edge locations.
- Messages are not encrypted: Messages are stored as plain text in KV. Do not use this for highly sensitive data without adding client-side encryption.
- TTL is fixed at 24 hours: The expiration time is not configurable per message.