Accounts, tiers and billing
Getting an account
Registration is at /register. While BETA_SIGNUP_CODE is set, a matching code
is required and requests without one are refused — which is why an open beta can
be closed without a deploy.
Existing users can bring others in directly: an invite creates a signed,
single-use link (/invite/{token}) that lands the recipient on a pre-filled
registration form. Password reset works the same way, through
/password-reset-request and a /password-reset/{token} link.
Once signed in you have two credentials, and they are not interchangeable:
- a session cookie for the web pages, and
- one or more API keys for the mobile app, the desktop client and anything you write yourself. Generate these from your profile page.
See the introduction for how the two differ in practice, including
why a missing key returns 422 and a bad key returns 401.
Your profile and garage
Your profile carries your display name, avatar and riding styles. The garage is your list of motorcycles, drawn from a vehicle catalogue the platform maintains:
| Search the catalogue | GET /vehicles/search |
| Browse by manufacturer | GET /vehicles/makes, GET /vehicles/models |
| One model | GET /vehicles/models/{model_id} |
| Its OBD-II parameters | GET /vehicles/models/{model_id}/obd |
| Report a correction | POST /vehicles/models/{model_id}/obd/report |
| Where the data came from | GET /vehicles/sources |
The OBD endpoints back the app's dongle support: knowing which parameters a given model exposes is what makes a live reading meaningful.
Performances
Rides can be scored against each other. POST /performances records an effort,
GET /performances lists yours, and GET /performances/leaderboard ranks them.
DELETE /performances/{effort_id} removes one.
Tiers
Plans are subtractive from the top: the app ships complete, the highest tier
holds every feature by construction, and lower tiers are what remains after
subtraction (app/services/features.py). The ladder is:
| Plan | Position |
|---|---|
| Free | Every signed-in user, including lapsed subscribers |
| Trial | Fourteen days with the Pegasus claim set |
| Centaur | Entry paid tier |
| Pegasus | Middle tier |
| Alicorn | Everything |
Each feature declares the lowest plan that includes it, so moving a feature between tiers is a one-value change rather than a scattered edit. Two kinds of feature are enforced differently, and the difference is deliberate:
- Server features gate an API route. A request without the claim is refused.
- Client features shape the app's own interface. They cost nothing at the margin, so they are enforced softly and circumvention is accepted rather than fought.
Per-plan grants can also be flipped at runtime without a deploy, through
GET /billing/admin/features and
PUT /billing/admin/features/{plan}/{slug}.
Billing
| What you are entitled to | GET /billing/me |
| Start a subscription | POST /billing/checkout |
| Stripe callbacks | POST /billing/stripe/webhook |
GET /billing/me returns the claims payload the clients read to decide what to
show. Checkout uses the price named by STRIPE_PRICE_ID, or the per-tier mapping
in STRIPE_PRICE_MAP. Webhooks are signature-verified with
STRIPE_WEBHOOK_SECRET, which is why that endpoint needs no API key.
:::tip Billing can be switched off entirely
While BILLING_ENABLED is unset, every entitlement check passes. A deployment
can run the full feature set without a payment provider configured, which is what
a self-hosted or pre-launch instance does.
:::