Faucet Service¶
The faucet is the platform's sign-up desk. A prospective user hands it an email; it checks the email, creates their account (via the platform service), and — for the free tier — seeds them at the fair-share cap so continuous accrual takes over from there. It emails the user their access token. Small FastAPI app on port 8500, backed by Postgres (creates its own database on first start).
The name is historical — it began as a credit "faucet" that dripped periodic top-ups. Today it's really the signup service; the ongoing top-ups now come from the credit service's fair-share accrual.
It's standalone — it owns its own tables and talks to just one service over HTTP: the platform service (/v1/auth/onboard), which does the actual granting. There's no weekly refill and the faucet no longer talks to the credit service directly — the credit service's fair-share accrual owns ongoing free credits.
Who gets what: free vs paid¶
| Free tier | Paid tier | |
|---|---|---|
| Who | University / allowed-domain email (.edu, …) |
Anyone else |
| Allowed to sign up? | Always | Only when open_registration is on |
| Starting credits | Seeded at the fair-share cap + marked accrual_eligible |
None (buy paid credits via Stripe) |
| Ongoing credits | Continuous fair-share accrual (credit service) | — |
The faucet's real job is the free tier: verify the student, seed them, and hand accrual the keys. Paid users just get an account.
Signing up (the claim flow)¶
POST /v1/faucet/claim {email}:
- Check the email's domain → decide free or paid (reject a non-university email if open registration is off).
- Lock the email's claim row and check the cooldown — serializes two simultaneous claims for the same email, so a race can't create two accounts or seed twice.
- Call platform
POST /v1/auth/onboardwithseed_accrual = (tier == free). For a free user that seeds them at the fair-share cap and marks themaccrual_eligible; a paid user just gets an account. If it fails, the whole thing rolls back — a failed sign-up doesn't burn the cooldown. - Record the user, then email them their access token over SMTP (Brevo). If only the email fails, the account still exists and the response says so.
The token is the user's identity — there are no passwords. Ongoing credits come from the credit service's fair-share accrual (documented there), not the faucet.
Admin¶
All admin endpoints require Authorization: Bearer <ADMIN_TOKEN> and fail closed if the token is unset.
- Registration toggle (
/v1/faucet/pool) —open_registrationgates whether non-university emails can sign up. - User management (
/v1/faucet/users/…) — list users, read one user's status, change their tier, and revoke / restore them. Platform auth reads…/users/{email}/statuson every request, so revoking a user here blocks their access everywhere.
Tables¶
| Table | Purpose |
|---|---|
claims |
One row per email — last_claim timestamp for the cooldown / claim lock |
users |
Registered users — user_type (free/paid), status (active/revoked) |
pool_config |
Singleton — just open_registration |
Endpoints¶
| Method | Path | Auth | What it does |
|---|---|---|---|
| GET | /healthz |
— | Health check |
| POST | /v1/faucet/claim |
— | Sign up with an email; emails back an access token |
| GET·PUT | /v1/faucet/pool |
admin | Read / toggle open_registration |
| GET | /v1/faucet/users |
admin | List registered users |
| GET | /v1/faucet/users/{email}/status |
admin | One user's status/tier (platform reads this for revocation) |
| PUT | /v1/faucet/users/{email} |
admin | Change a user's tier |
| POST | /v1/faucet/users/{email}/revoke · /restore |
admin | Disable / re-enable an account |
Configuration¶
| Variable | Default | Description |
|---|---|---|
FAUCET_DATABASE_URL |
— (required) | Postgres connection string (DB auto-created if missing) |
FAUCET_PORT |
8500 | Service port |
ADMIN_TOKEN |
— | Shared secret for admin + the onboard call |
PLATFORM_URL |
http://platform:8100 |
Where accounts are created + seeded |
ALLOWED_EMAIL_DOMAINS |
.edu,gmail.com,atomicmail.io |
Free-tier domains (comma-separated) |
CLAIM_COOLDOWN_HOURS |
~0.017 (≈1 min) | Minimum gap between claims per email |
SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS/SMTP_FROM |
Brevo defaults | Email delivery |
CORS_ORIGINS |
* |
Allowed origins |
RATE_LIMIT_PER_MINUTE |
120 | Per-IP rate limit |
Running & testing¶
# Unit tests need a Postgres (test compose exposes one on host port 5433):
docker compose -f docker-compose.test.yml --profile faucet up postgres -d
pytest tests/faucet_service/ -v --ignore=tests/faucet_service/test_docker.py
# Full stack for the Docker integration test:
docker compose -f docker-compose.test.yml --profile faucet up --build -d
pytest tests/faucet_service/test_docker.py -v
Coverage: test_claim / test_claim_flows (domain rules, cooldown, tiers), test_claim_concurrency (no double-signup under a race), test_pool (open_registration), test_admin_users (user management), test_endpoints (claim + seed wiring), test_email_verify (SMTP), test_docker (deployed).