Privacy policy
Interstate Tracker records which parts of the U.S. Interstate Highway System you have driven, exit to exit. Doing that needs your location. This page says what the app reads, what leaves your phone, where it goes, and how to get rid of it.
One person built Interstate Tracker and one person runs the server it talks to. That person is me, Andrew Parlette. There is no company and no team.
The app records only during a trip you started. Without an account nothing leaves the phone. With an account, each finished trip and a simplified trace of it are sent to my server and stored there. That is location history: roughly where you drove, and roughly when.
What the app records on your phone
The app reads your location only while a trip is recording. You tap Start trip before you set off and Stop when you arrive, and an ongoing notification is visible the whole time, with a Stop action on it. The app also ends a recording itself when the vehicle has stayed inside 200 meters for 10 minutes, or when no GPS fix has arrived for 10 minutes, so a trip you forget to stop does not keep reading your location.
A recorded fix is a latitude, a longitude, the time, and the accuracy radius Android reported — the radius in meters inside which the phone put the true position with 68 percent confidence. The app asks for a fix every second until it is sure which road you are on, and one every 5 seconds once it is settled, going back to once a second whenever the road it settled on stops fitting.
The app asks only for “while using the app” location. It does not hold Android’s background-location permission, so it cannot read your location when no trip is running. It does not hold the activity-recognition permission either, so it reads nothing about your physical activity: no steps, no exercise, no heart rate, no Health Connect. It holds no health or fitness data of any kind. Android will also list a wake lock and a network-state check, both belonging to the scheduler that uploads a finished trip, and neither reads anything about you.
The app has no microphone permission and records no sound. Spoken milestones are off until you turn them on, and they speak through Android’s own voice. When you import or export a file, Android’s file picker hands the app that one file and nothing else on your device.
Working out which Interstate you were on happens on the phone, against map data that ships inside the app, so a drive counts with no signal at all. The server is sent the result, and it keeps the trace so the same drive can be worked out again later from better map data.
What stays on your phone
The full-resolution GPS points of every trip stay on the phone. Nothing prunes them, and no timer removes them; they are there until you delete the trip, wipe the app’s data, or remove the app. So are the trip list, the map, the mileage and the awards the app works out from them.
Once you sign in, the phone also keeps a cached copy of what the server serves it: your trip list, your coverage, your awards, your clinches and your route history. A trip recorded on another device shows here as “Trip not on this phone”.
What leaves your phone, and when
Without an account, nothing, with one exception you control: a problem report, described at the end of this section. A phone that never signs in and never sends a report never opens a connection to my server.
Once you are signed in, a trip is sent after it has finished recording, when the phone next has a network. Nothing is sent while you are driving.
What is sent:
- The trip record — when it started and ended, to the millisecond your phone’s clock reported, how far it went, how it was recorded (a trip you drove, a GPX file you imported, or a section you claimed by hand), and any name or notes you typed.
- A simplified trace — the GPS points thinned out so the kept line stays within 50 meters of the one your phone recorded, with every timestamp floored to the minute. Each kept point carries its accuracy radius. The full-resolution trace is never uploaded.
- The sections you were matched to — each stretch as a position along a named route in a named state, with a direction, how it was recorded, and when you entered and left it. For each stretch you completed between two interchanges, a second record names those two interchanges, the direction you were going, the time you passed each, the miles as driven, and how confident the match was. Neither record carries a coordinate: a position on a route is a distance along it.
Each of these also carries your phone’s model name, such as SM-S911U, and the time your phone thought it was, so a sync interrupted halfway can be retried without duplicating anything. Every phone of that model sends the same string. The app mints no identifier of its own and holds no advertising identifier.
Every request the app makes also names itself in its User-Agent header, the way a browser does: the app’s version and build number, the Android release and the phone’s model, as in InterstateTracker/1.1.0 (5; Android 14; Pixel 7). Android’s own default already sent the release and the model; the app adds its version so the server’s log can say which build a failing request came from. Every phone of that model on that build sends the same string.
Trips you recorded before signing in stay where they are. Signing in does not sweep them up. The app holds back every trip that existed at that moment and sends none of them until you tap Upload N trips. Uploading copies them: they stay on the phone as well. If you tap Keep them on this phone they stay there, and the offer stays in Settings. Trips you record after signing in sync on their own.
A deletion follows a trip. If you delete a trip the phone has already sent, the phone sends the deletion on its next sync. Renaming a trip, or editing its notes, travels the same way.
Achievements are never uploaded by the phone. The server works them out from the facts above, and it refuses an achievement a client tries to send it.
Reporting a problem
Settings, then Report a problem, sends one report, once, when you press Send. Nothing about a report is gathered or sent at any other time, and nothing is queued: if the send fails the app says so, and nothing goes until you press Send again. A report does not need an account.
What a report carries:
- Your message, if you typed one.
- The app’s version and build type, and the Android release, SDK level, manufacturer and model.
- Which of the four permissions are granted, and what the app can therefore do.
- Whether the phone is signed in, how many trips are waiting to upload, and whether the phone holds a copy of your coverage.
- Your last ten trips as summary rows: when each started and ended, how far it went, its Interstate and new miles, how it was recorded, the routes it touched, whether it has been uploaded, and how many points it holds. No coordinates, and not the name or notes.
- The app’s own recent log lines, at most 300. Android lets an app read only the lines it wrote itself.
- The IP address the report was sent from and the User-Agent header the request carried, both kept with the row for the same 90 days. The app does not put them there: my server writes down the address the request arrived from and the name the request gave for itself, and nothing reads either afterwards except to look into abuse.
If the phone is signed in, the report is sent with the phone’s sign-in token so I know which account it belongs to. The report itself never contains your email address.
One thing is sent only if you tick the box: the GPS trace of your most recent trip. That trace is the full-resolution one, thinned to at most 5,000 points when the trip has more. The box is off every time the form opens.
Reports land in a table on my server, and once an hour I am emailed a summary of the new ones: the account’s address if the phone was signed in, the app and device, whether a trace came along, and your message. The bundle itself I read from the table by hand. Reports are deleted 90 days after they arrive, and deleting your account deletes the reports it sent at once. A report sent with no account cannot be tied to you afterwards, so it cannot be found and deleted on request except by its time and its message.
Your account
An account is optional. You reach it from the app’s settings, nothing in the app asks you for one, and it is created the first time you sign in.
On the phone you type your email address and a six-digit code is mailed to you. There is no password. The code stops working after 5 wrong tries or 10 minutes, whichever comes first. Asking for a code answers the same way whether or not an account exists, so the form cannot be used to find out who has one.
Once the code is accepted the phone holds a token, kept in the phone’s encrypted preference store. It expires after 90 days, and using the app does not extend it. Only a one-way hash of the token is stored on the server, so a copy of the database is not a set of working logins, and a token that goes missing has to be replaced.
The phone also sends a name for itself. It does not ask you for one: it sends the model name Android reports, such as SM-S911U, which every phone of that model shares.
In the web app you log in with an emailed sign-in link, good for one use and 15 minutes, or with a passkey once you have added one. A passkey’s private half never leaves your device. What is stored is the public half, an identifier, a label you can read, and the key’s own details: a signature counter, how it can be reached, its model identifier, whether it is backed up, and when it was last used. Your fingerprint or face is checked by your device and never reaches me. An Operator sign-in box that takes a token pasted by hand is on a page of its own that nothing links to, for my own use. The page you log in on does not carry it.
A sign-in link carries its credential in the part of the address your browser never sends. The link is in the mail sent to you, and it is in your browser’s history for as long as your browser keeps history — but it reaches neither my server, nor anything on the network in between, nor the access log, and the page clears it from the address bar as soon as it loads. Opening the link does not spend it: the page it opens carries a Log in button, and pressing that is what logs you in. The link is good for one use and 15 minutes either way, and a mail scanner that follows links on your behalf leaves it for you.
Per account, the server holds:
- Your email address, used to identify the account and to send a code or a link.
- Your trips, traces, manual claims and achievements, stored as an append-only log of facts. Deleting or editing something adds a fact rather than rewriting one. Every screen shows the current result after those facts are applied, so a deleted trip is gone from your trips, your map and your statistics the next time any of them loads.
- Your phone sign-ins, one row each: a hash of the token, the phone’s model name, when it was created, last used and expires, and whether it was revoked. Expired and revoked rows stay, inert, until the account is deleted.
- Your browser sessions, one row each, with a coarse label such as “Firefox on Linux” worked out from the User-Agent your browser sent. The raw header never reaches the row, though it does appear in the access log like every other header. A session ends when you sign out, after 30 days without use, or a year after it began, whichever comes first.
- Your passkeys, if you add any. A revoked passkey leaves its row behind, inert, until the account is deleted.
- Pending codes and links — a one-way hash of the credential and the IP address the request came from, held until the credential is spent or swept. A spent phone code’s row is deleted on the spot.
Settings in the web app lists your signed-in phones, your signed-in browsers and your passkeys, and revokes any of them. A phone cannot revoke anything; only a signed-in browser can. If you cannot reach a browser, email me and I will do it by hand.
You can ask for 3 codes or links for one address in 15 minutes, and 10 from one IP address in an hour. Opening a sign-in link is limited to 30 tries an hour from one IP address, as is trading a phone’s token for a signed-in browser. Across everything, the server stops sending after 200 messages in a day. The hourly sign-up list to me counts toward that number but is never held back, because it is the message that would tell me a flood was happening. The first message the cap stops on a given day also emails me to say the cap has fired, and that notice carries nobody’s address.
Deleting, and signing out
Deleting a trip deletes it everywhere. Delete it in the web app and the deletion goes at once; delete it on the phone and the phone sends the deletion on its next sync. The same deletion erases that trip’s uploaded trace in the same step, with no operator involved and no email needed. If the phone’s copy of the trace arrives after the deletion, it is erased on arrival.
You can delete a trace and keep the trip. Each trip’s row in the web app shows whether a trace is held for it, how many points it has and what span it covers, and offers Delete the trace, keep the trip. The trace is erased at once and the miles that trip earned stay. The trade is permanent: a trip whose trace is gone can never be matched again, by me or by a better matcher later.
The rest of a deleted trip’s content is a step I run by hand. The trip record and the sections it was matched to stay in the log, hidden from every view, until I run the redaction tool. That tool overwrites them in place and leaves a record that a deletion happened, not a record of what was deleted. Email me to have it run.
Signing out on the phone returns that phone to device-only. It sends no request at all. The trips that phone recorded stay on it. The cached copy of your account is emptied, so a trip that reached the phone only from the server stops showing until you sign in again. Your account, and everything already uploaded, stays on the server until you delete the account.
Signing out does not revoke the phone’s token on the server. The token lapses after 90 days, or when you revoke it under Signed-in phones in the web app.
Settings, then Delete all data, wipes that phone and nothing else. It removes the phone’s own trips and points and signs it out. The server is never told.
Delete account is in the web app, under “Your data”. It asks you to confirm once and then runs. In one transaction it takes your trips, traces, claims, achievements, browser sessions, phone sign-ins, passkeys, any pending sign-in codes, and any problem reports a signed-in phone sent. There is no undo and no grace period. The browser you ran it from stops working the moment it commits, and a phone still signed in has its token rejected on its next sync. If you want a copy of anything, download it first.
One thing account deletion does not touch is a beta-tester sign-up made on the website, because it is not part of an account. Ask me and I will remove it.
Getting a copy of your data
On the phone, Export all as JSON writes a file through Android’s file picker holding your trips with their full-resolution GPS points, your achievements, your coverage and your traversals. A single trip can be exported as GPX, also from its raw points. Import GPX is on the phone only; the web app has no import.
The web app offers three formats under “Your data”.
- Everything, as JSON — your account identity, the complete log of facts your account has recorded, the coverage folded out of that log, and the metadata for any passkeys you have. It leaves out passkey key material, because an export carrying it would turn one careless file into a way into your account. Three things the server holds are not in it either: your browser sessions and your phone sign-ins, which Settings lists on screen, and a beta-tester sign-up, which is not part of an account.
- Trips, as GPX — one track per trip, built from the simplified 50-meter, minute-floored trace.
- Coverage, as GeoJSON — one line per clinched exit-to-exit stretch. The lines are drawn on road geometry taken from the public map that ships inside the app, which is the one thing in any export that did not come from you.
An exported file is an ordinary file once you have saved or shared it, outside the app’s control.
The website and the web app
The web app makes no third-party request of any kind. No map tiles, no icon sprites, no web fonts, no script from a CDN, no analytics, no crash reporting. The map carries no basemap at all: it draws the road network and your coverage, both served from my server, on a plain background. The browser is told to refuse everything else by a content-security policy of default-src 'self', and a test asserts that the map style asks for no external URL.
The website works the same way: static pages, self-hosted fonts, no analytics, and one inline script, on the beta page, that posts the sign-up form.
Your browser holds five things for the web app: a copy of your coverage, the tag the server uses to tell whether that copy is stale (it includes your account id), a queue of writes waiting to reach the server, a random identifier for this browser, and whether you chose the light or the dark theme. Your sign-in itself is a cookie, first-party and strictly necessary, which is why there is no cookie banner to click.
Signing out clears everything about your account, including anything still waiting in the queue, and it warns you first if the queue is not empty. The theme preference stays, because it says nothing about you. Closing the tab without signing out clears nothing; that cache is as private as your browser profile and your device already are.
Reporting a problem in the web app
A footer runs under every screen of the web app, with links to this site, to this policy, to the donate page and to Report a problem.
That link opens a form, and it sends one report, once, when you press Send. Nothing about a report is gathered or sent at any other time, and nothing is queued: if the send fails the screen says so, and nothing goes until you press Send again. A report does not need an account.
What a report from the web app carries:
- Your message.
- Which build of the web app the browser is running, and when that build was made.
- The address of the screen you were on — the path only, never the query string and never anything after the
#. - The size of the browser window, the theme you chose, and whether the browser was online.
- Whether your coverage had loaded, and the last failure the app recorded loading it.
That is the whole report. It carries no email address, and it does not name your account even when you are logged in: the server works out whose a report is from a phone’s sign-in token, and the web app neither holds one nor sends anything else that would identify you. A report sent from a browser cannot be tied to you afterwards, so it cannot be found and deleted on request except by its time and its message.
Reports from the web app land in the same table as the phone’s, beside the IP address the request came from and the browser’s User-Agent, and once an hour I am emailed a summary of the new ones: the build, the browser, the screen and your message. Reports are deleted 90 days after they arrive. The report goes to my own server, like everything else the web app asks for.
Beta-tester sign-ups
The website has a form for joining the beta. Sending it stores your email address, the name you typed, the region you typed, your answer to the consent box, the IP address the request came from, and the time. The consent box is required, so the stored answer is always yes.
Once an hour, any sign-ups I have not seen yet are mailed to me in one list through Postmark: each row’s time, email address, name, region and consent. Nothing else reads the table and nothing sweeps it. Rows stay until I remove them by hand, and so does the IP address, which is not swept the way a sign-in request’s address is.
A sign-up is not an account. Creating an account later does not merge the two, and deleting an account does not remove the sign-up. Email me to be removed from it.
An invitation is a note from me, by email, when a testing slot opens, and it carries an opt-in link for the closed test on Google Play. The form itself has no connection to Google Play: nothing you type here reaches Google, and accepting the invitation is a separate step you take on Play. Your name is published nowhere unless you separately agree to be credited.
Where it lives, and who can reach it
Everything the server holds sits on one rented virtual machine in Orangeburg, New York, in the United States. It is not hosted in the EU. Contabo rents me the machine and has physical custody of the disk it runs on; they are not sent your data as a service.
I operate that machine, and access to it is mine alone.
Traffic between your phone or your browser and the server is encrypted with TLS. Ports 80 and 443 are the only ones open to the internet; the database and the API publish no port of their own.
Your data is not end-to-end encrypted, and it could not be while the server does what it does. The server folds every fact you send into the coverage map the web app draws, works achievements out of it, and re-derives coverage from your traces when the map or the matcher improves. All three require reading your location in the clear.
Three other parties are in the picture, and none of them is sent your driving.
- Postmark carries the mail. See “Mail”, below.
- Cloudflare answers DNS for interstatetracker.com and does nothing else. It tells a browser where the server is; the request then goes straight to the server rather than through Cloudflare.
- Google Play services provides the location on your phone. It is an Android system component rather than part of Interstate Tracker, and what Google does through it is governed by Google’s own privacy policy at https://policies.google.com/privacy. Google is not sent your trips, traces or coverage. Google Play will distribute the app once it is uploaded there.
What I do not do
There is no analytics, advertising or crash-reporting service anywhere in this project: not in the Android app, not in the web app, not on the website, not on the server. A problem report, from the phone or from the browser, is sent only when you press Send. There is no advertising identifier, and nothing tracks you across other apps or sites. Nothing is sold, and nothing is shared beyond the parties named above. No marketing mail is ever sent.
Three kinds of message leave the server: a sign-in code to a phone, a sign-in link to a browser, and the hourly beta sign-up list to me. Postmark carries all three. What reaches Postmark is the recipient’s address and the message itself, which for the sign-up list means each new signer’s email address, name, region and consent. Nothing about anyone’s driving ever reaches Postmark. Postmark keeps its own delivery records, governed by its privacy policy at https://postmarkapp.com/privacy-policy.
A sign-in code or link stays in the mail sent to you until you delete the message.
How long it is kept
Until you delete it. There is no retention window and nothing expires on a timer. No job ages out trips, traces, coverage or achievements.
Traces are kept because a better map-matcher may be able to re-derive more accurate coverage from a trace than the matcher that first read it. That is the whole reason a trace is held, and “Deleting, and signing out” is where you end it.
Two things are swept automatically. Short-lived credentials: expired sign-in codes and links, and sessions that can no longer authenticate anyone, are deleted hourly. And problem reports, which are deleted 90 days after they arrive.
The database is dumped every night at 03:17 UTC and the 7 most recent dumps are kept, so a deletion is out of the backups within about a week. A dump is compressed, and it is not encrypted. It sits on the same machine as the live data, readable only by the account that made it. Off-site copies are planned and are not in place.
What is written down about a request
Every request to the server, from the app, the web app and the website, passes through Caddy, the reverse proxy in front of all of it. Caddy writes one line of JSON per request: your IP address, the method, the host, the full address requested including anything after the question mark, the request and response headers, the TLS details, the status, the size, and how long it took. The request headers include the app’s User-Agent, which names the app’s version and your phone’s model. Your session cookie and your Authorization header are never written — Caddy leaves credential headers out of the log — and the token in a sign-in link is replaced with the word REDACTED, though a sign-in link no longer puts one in the part of the address that is sent. Each file rolls at 10 MB and 5 rolled copies are kept, so the log holds roughly the last 50 MB for each site.
That is operational logging, for finding faults and looking into abuse. It is not aggregated into visitor counts or dashboards.
The API’s own application log is narrower: the request method, the path, the response status, and how long the call took. It never holds your IP address, the query string, a header or a body.
The database writes down any query that takes longer than half a second, including the text of the query, which can include the contents of a fact being stored. The database and API container logs roll at 10 MB with 5 copies kept, the way Caddy’s do.
A failed attempt to send you a code or a link is logged as a request id and the provider’s reason for refusing it, with your address and the credential cut out of that reason first.
Four things put an IP address in the database rather than in a log. The first is the address a sign-in code or link was requested from, stored beside the hashed credential so a flood of requests can be traced back afterwards. The hourly sweep deletes that row once the credential is spent or expired, so the address is gone within about an hour and a quarter at the outside.
The second is the address a problem report was sent from, held on the report’s own row for the same reason. Nothing reads it, and it goes when the report goes, 90 days after it arrived.
The third is the address a beta-tester sign-up came from, described above. It stays as long as the sign-up does, because I remove those by hand.
The fourth is the counting behind every limit on the server, the ones above included. Each limit is one row. The row holds the thing being counted, when its window opened, when the window closes, and how many requests it has counted — those four values and nothing else. No credential, nothing about what was asked for, and nothing about whether an address has an account.
The thing being counted is an IP address for most of them, and for the rest it is an email address somebody asked for a link or a code or a beta invitation for, or your account id where the limit is per account — ten problem reports a day, a thousand trip uploads an hour. One counts nothing about anybody: the server’s own daily mail total, which is a date and a number.
A row lasts as long as the limit it counts, and the hourly sweep removes it after that. So fifteen minutes plus up to an hour for a per-address limit, an hour plus up to an hour for a per-IP one, and a day plus up to an hour for the per-account report limit and the daily mail total — about twenty-five hours, the longest anything here lives. Deleting your account does not reach these rows. It does not need to: the id in one no longer names anything once the account is gone, and the row is swept within about a day either way.
These counts used to be held in memory, which meant a restart erased them. The server restarts on every deploy, and restarts itself if it crashes, so the limits kept stopping just when they were needed. They are rows now. If the database cannot be reached the server counts in memory again until it can, so a limit is never simply switched off.
Children
Interstate Tracker is not directed at children under 13 and does not knowingly collect information from them.
Changes to this policy
This page is the policy. When something changes, this page changes and the date at the top changes with it. There is no version history to read.
Contact
Questions about this policy, a deletion request, or anything here you think is wrong: admin@interstatetracker.com