Skip to main content

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. A missing key and a bad key both return 401.

Creating an account means accepting the Terms of Service and the Privacy Policy (/guide/legal/terms, /guide/legal/privacy). GET /legal/current returns the live version of both texts; a sign-up without terms_accepted is refused with 400 terms_not_accepted once TERMS_ENFORCE_FROM has passed, and the account records the date and version it accepted.

Your account page: a copy of your data, and deleting your account​

Every signed-in account has a page at /account (on the public site: https://fastgis.eu/account). Signing in on the web lands a rider there; accounts with the web console (managers and fleet operators, and administrators) land on /dashboard, which carries the same delete panel.

Download a copy of your data (GDPR Art. 15 and Art. 20). One click queues an archive of everything held about you: profile, garage, routes, library, surveys, rides, diagnostics, events, shares, emergency contacts, support conversation, subscription state, the files you uploaded (vehicle manuals and event files) and what the desktop client keeps. A README inside says what each file is. The archive is built in the background. When it is ready, it is emailed to you and listed on the account page. Either link downloads it once, within 6 hours, and only while you are signed in as that account. You can ask once an hour.

Delete my account (Art. 17). Type your username to confirm. Deleting starts a 30-day window before your data is destroyed:

  • At once, and for good: public links and shares stop working, published stories and board listings come down, your emergency contacts are deleted (they are other people's details), friends, group memberships, push notifications and live location sharing end, and you are signed out everywhere.
  • After 30 days: everything else — routes, library, rides, captures, surveys, leaderboard runs, vehicle reports, support conversation and workspace.

If you have shared tracks, the panel lists them, none ticked. A track you tick stays published after your account is gone, edited first (the start and end of the ride and the places you stopped are removed, and we do not claim the result is anonymous). You then get a one-time code, on screen and by email. That code is the only way to remove those tracks later, at /withdraw-tracks.

To cancel, use the link in the email we send you, or sign in again within the 30 days: you land on a screen with the date, Keep my account and Delete now instead of waiting. Keeping restores what was held; what went at once does not come back. You can still download a copy of your data from that screen.

From the apps (the API). The same two rights are open to an app holding the account's X-API-Key, under /auth/account/* (the API reference, tag auth-account). They do the same as the account page:

  • POST /auth/account/delete with {"confirm": "<username>", "keep_tracks": [...]} is the same deletion, and it revokes the key that called it. The answer is {erase_after, receipt_code, kept}. The receipt code is in that body only (and in the email), never in a URL. A wrong username answers 400 and changes nothing.
  • GET /auth/account/shared-tracks lists the tracks that could be kept: id, name and length, no geometry.
  • POST /auth/account/export queues the archive (202). The hour is shared with the account page, so a second ask from either answers 429 with retry_after_s. It answers 503 when the build queue is down, and that attempt does not count toward the hour.
  • GET /auth/account/export says whether an archive is ready and until when. The download itself stays on the account page, signed in.

An account inside its 30-day window gets 401 from all four: its keys were revoked, and signing in gives it no new one. Its way back is the web.

The desktop's Settings → Account uses these four (the desktop guide); deleting from there also clears that browser's own copies of the account. The rider app's Settings → Account (More → Connection, signed in) uses them too (the rider app); deleting there signs the phone out and clears the account from it, as a sign-out does, and a sign-in during the window shows the date and opens the web screen.

Your profile and garage​

Your profile carries your display name, avatar and riding styles. The garage is your list of vehicles, drawn from a vehicle catalogue the platform maintains:

Search the catalogueGET /vehicles/search?q=&kind=
Browse by manufacturerGET /vehicles/makes?kind=, GET /vehicles/models?make=&kind=
One modelGET /vehicles/models/{model_id}
Its OBD-II parametersGET /vehicles/models/{model_id}/obd
Report a correctionPOST /vehicles/models/{model_id}/obd/report
Where the data came fromGET /vehicles/sources

kind is motorcycle (the default) or car, and the three list endpoints answer with that kind alone. Leaving it out gives motorcycles, so a client written before the car list existed is unaffected.

The OBD endpoints back the app's dongle support: knowing which parameters a given model exposes is what makes a live reading meaningful.

The 4x4 car list​

kind=car is a named list of 4x4 model families — Land Cruiser, Defender, Range Rover, Hilux, Jimny, Duster, G-Class and thirty-odd more — rather than every car ever registered. It has to be a named list: no vehicle register says which cars drive all four wheels. Every one of them is registered in the same EU category as a hatchback, and the body code that would mark an off-roader is on fewer than two thousand cars in the whole Dutch register. So the list is kept in the open, in the repository, and each family is counted against the register before it ships.

A car's detail (GET /vehicles/models/{model_id}) carries two extra fields:

FieldWhat it says
route_vehicle"4x4" — the speed row a pick should set, which you can then change to Car
four_wheel_drive"all" when every version of that model drives four wheels, "some" when the family also has two-wheel-drive versions

four_wheel_drive: "some" is the honest answer for a RAV4, a Duster or a Hilux, where 4x2 versions are on the road under the same name: the apps say 4x4 versions exist rather than telling you your car is one.

A car's consumption comes from the register too, as the combined figure the model was type-approved at — measurement on the spec reads type_approval_wltp, or type_approval_nedc for cars older than the WLTP cycle, and the two are never mixed. It is a laboratory figure and optimistic off road, so it arrives as a starting point you can edit. The fuel tank is still yours to type: no register records it.

Each garage vehicle also says how its routes are timed on unpaved ground: route_vehicle on POST /auth/garage and PATCH /auth/garage/{vid} is one row of the vehicle speed table — sport, touring, adventure or enduro for a motorcycle, car or 4x4 for a car — and "" means the kind's default (Adventure, Car). A row the kind cannot have is refused; a bicycle or walking entry has none. The rider app sends the default bike's row as vehicle on its route requests (see the rider app page, "How the ETA is estimated": rider app).

Fuel you carry beside the tank​

On a 4x4 it is common to carry jerry cans, so a garage vehicle can say how many and how big: extra_tank_count and extra_tank_size_l on POST /auth/garage and PATCH /auth/garage/{vid}. You give the count and the size — two cans of twenty litres — and the totals come back derived, never typed:

Field
extra_fuel_lthe litres you carry, count × size
extra_range_kmwhat those litres are worth at your consumption figure

They are a second range, not a bigger tank. range_km stays your vehicle's own tank times its consumption, because that is the range the vehicle can actually reach while moving: fuel in a can is not available until you stop and pour it. So the apps show the extra beside the range ring rather than inside it, and anything that plans fuel stops goes on using range_km.

Carried fuel is your kit, not the manual's figures, so picking a model from the catalogue never fills it in and never locks it — unlike the tank and consumption fields, which a pick or a manual read can supply. Set extra_tank_count to 0 when the cans come off.

Your service schedule, in your own language​

A garage vehicle carries its own service schedule: maintenance on POST /auth/garage and PATCH /auth/garage/{vid} is a list of up to twenty items, each with a name, an interval_km or an interval_months (at least one — a brake fluid change is "every 24 months" with no distance at all) and the last_done_km you have recorded.

Each item may also name which service it is:

FieldWhat it says
namethe words to show: the catalogue's published wording, or your own
itemthe catalogue's key for that service — oil_change, air_filter, valve_clearance, brake_fluid, chain_lube and the rest of the list GET /vehicles/models/{model_id} answers in

item is optional in both directions, and that is the point. When you pick a bike from the catalogue, the apps send the key along with the wording, and the apps can then say the item in your language instead of showing the catalogue's English sentence — the same words Read it uses below. When you type an item in yourself there is no key, and your own words are shown; a key outside the catalogue's list is refused rather than stored. A vehicle saved before the key existed answers item: null, and its words are shown exactly as they were.

Your own manual​

A vehicle can keep your own owner's manual, so that it is always with the bike rather than in a drawer. POST /auth/garage/{vid}/manual takes the PDF as a multipart upload, up to 60 MB; the content has to really be a PDF, not just a file called one.

The manual is stored as a private document in your Library and linked from the vehicle:

Upload or replace itPOST /auth/garage/{vid}/manual (multipart file)
Open itGET /grass/catalogue/items/{item_id}/file
Remove itDELETE /auth/garage/{vid}/manual
Read what it saysPOST /auth/garage/{vid}/manual/read, then GET the same path

The vehicle's JSON then carries manual_item_id and a manual block with the title, the size in bytes and the page count (pages is null when the PDF could not be read — the file is kept either way).

It is yours alone. The file is served by one route, which reads the record as you before it answers, so another account gets a 404 — the same answer as for a record that does not exist. A manual cannot be shared, published or re-exported to anyone else, and no document record is created by anything but this upload. Uploading again replaces the manual: the previous one is removed outright rather than put in the trash, and so is the file. Deleting the vehicle takes its manual with it.

Your manuals travel with the rest of your data: they are in the documents/ folder of a download of your data (as PDFs, named after their Library record), and they are erased with the account.

Reading your manual​

Read it pulls the specs and the service intervals out of your own PDF and keeps them against your bike. POST /auth/garage/{vid}/manual/read starts it and answers 202 straight away — reading a 60 MB manual is not a request's work — and GET /auth/garage/{vid}/manual/read tells you how it is going and, when it is done, what the manual said.

Every figure carries where it came from: the source is your manual, the page it was printed on, and, when your bike is a catalogue model, the catalogue's own number beside it so you can see the two agree. A figure whose line could not be found again on any page is shown with no page and marked unverified — a page number nobody can check is worse than none.

It says plainly what it could not read. Most manuals in the world are not laid out like the ones we have patterns for. A brand whose service chart marks its cells with pictures (which no text extraction can see), or a manual that is a scan with no text in it, finishes with a plain answer rather than a silence:

codeWhat it means
readthe specs and the schedule were read
specs_onlycould not read the schedule of this manual; the specs page was read
nothing_readnothing read — with a reason: it is a scan, or the brand is not one we can read yet

A read that ends nothing_read is still a finished read (state: "done"): failed is kept for the job itself going wrong. The brand is recorded either way, including when it is a brand we have no patterns for — that list is what decides which brand is taught next.

text_pages says how many pages of text came out of the PDF, which is 0 for a scan — it is not the manual's page count, which is pages on the manual block beside it.

A read describes one PDF. Replace the manual and the read goes with the book it described, rather than showing the old manual's intervals under the new one.

Your riding pace​

The rider app learns how fast you really ride against the plan — one factor on roads, one on slower stretches such as tracks — and remembers it per vehicle (see "How the ETA is estimated" on the rider app page). The account can hold a copy, so that a new phone starts from your pace rather than from nothing and the desktop planner can time a route at it (a fourth pace step, Mine). The routes below are live. The desktop planner reads them (only reads: it never writes your pace); the rider app starts writing and reading them in its next release.

Every vehicle's pace, and the limits a client appliesGET /auth/pace
Store one vehicle's pacePUT /auth/pace/{vehicle}
Forget it, every vehicleDELETE /auth/pace

{vehicle} is motorcycle, car, bicycle or walk. A record carries fast_factor and slow_factor (actual ÷ planned time, kept between 0.25 and 3), the km behind each, rides, updated_at and split_kmh, the road/track split it was learned under (30–50 km/h). The newer updated_at wins: a write older than what is stored changes nothing, and every answer returns what is stored now with applied: true or false, so a phone that lost the race takes the account's copy. A pace counts once 20 km are behind it, and is applied damped halfway to the plan (limits.seed_min_km, limits.seed_damping).

The pace is in your data export (riding_pace.json) and is erased with the account.

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:

PlanPosition
FreeEvery signed-in user, including lapsed subscribers
TrialFourteen days with the Pegasus claim set
CentaurEntry paid tier
PegasusMiddle tier
AlicornEverything

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 toGET /billing/me
Start a subscriptionPOST /billing/checkout
Stripe callbacksPOST /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. :::