Privacy
This page is the source. If the app changes, this page changes in the same commit.
- Responsible
- Robin Speekenbrink, Haarlem, the Netherlands. A private individual, not a company.
- Applies to
- The Stapel Android app and this website.
- Last changed
- Status
- The app is in the Play Store. This statement describes the app as it is released there.
What Stapel does not do
- No account
- No sign-in
- No user id
- No analytics
- No crash reporting
- No advertising id
- No ad network
- No offers feed
- No Firebase service
- No Google Wallet
- No location history
- No Stapel server
There is no Stapel server for your data to go to. It does not exist, and that is an architecture rather than a promise. What I do claim is narrower and more precise: no network call you did not ask for. Four calls, all described in full below. None carries a card number. Two of them do say something about where you are: refreshing the store list, because the parts it asks for follow from where your phone is, and looking up an address, which happens only when you press that button. Exactly how much is set out below.
What Stapel stores, and where
| Data | Where it lives | Leaves the device |
|---|---|---|
| Card numbers and barcodes | Local database on the device | Only if you copy one, or make a file of them and send it. An arrival notice that shows the barcode also reaches a paired watch. |
| Card names, chains, notes, folders, ordering | Local database on the device | Never |
| Voucher expiry dates and balance text | Local database on the device | Never |
| Your location | In memory to work out which shop is near. One thing does persist: per country, the app remembers the point its parts were selected against, coarsened to a grid cell rather than kept as a coordinate. That is what lets the app tell you your shop data is for somewhere else. It lives in the settings store, which goes into your device backup | Never as a coordinate. For a country published in parts, which parts are requested shows which parts of the country the phone is near |
| Shops you pin yourself | A separate table the sync never touches | Only if you compose and send a mail about one yourself |
| A PDF or photo you hand it to read | Stays where it is. The app reads it and does not copy it | Never |
| A card you hand over as a file | A temporary file in the app's own directory | Only to the app you pick in the share sheet |
| Settings | Local preferences on the device | Never |
Every network call the app can make
Four. This is the complete list, not a selection.
Stapel · network calls
1. Refreshing the store list
On by default
- When
- Automatically, at most once a month, on unmetered wifi unless you allow mobile data, and once right after a country is switched on. It waits for wifi by default. Switch "Download over mobile data too" on and the automatic run goes over mobile data as well. Press "Update now" yourself and it goes either way, because you asked it to.
- What
- One unauthenticated
GETper chosen country to one fixed, public address onraw.githubusercontent.com. No account, no device id, no card data, no analytics, no cookies. The address reveals which country you fetch, nothing more. The request does name the app and its version, for exampleStapel/0.4.0, so the other end can see which version is still fetching. No device model, no Android version, no locale. - The list of countries
- The same run also makes one
GETto one fixed address for the list of countries that have shops published. That address names no country and carries no location, so nothing can be read from it but that a Stapel is running. The list is signed the same way and checked the same way. All it decides is which countries you can switch on in the app, so that a country can be added without you having to install a new version. - Which countries
- The ones you switch on yourself. With "fetch a neighbouring country at the border" on, a country can also be added without you switching it on: the app works out on your phone how far a location fix is from the country outlines, and switches on a country whose border is close by. That fix only exists when you already asked the app for your location, never in the background, and no geofence or background fix is used. The fix is not sent anywhere and not stored. What then goes over the wire is the ordinary request above, so the address does reveal which country it is. This switch is off by default.
- Countries published in parts
- All thirty countries are published in parts, the Netherlands included, which is the one that ships with the app. Every one of them is published as parts alone, with no whole-country file beside them; the United States had to be first, because it does not fit in one file. Since 0.9.1 that holds for every country. For fetching it makes no difference: everywhere the app first fetches the index, and then one file for each part it needs and no more. Which parts those are follows from where your device is: every part is a box on the map, and the app asks for the boxes lying near that point. The coordinate itself is not sent and not stored, but the sequence of addresses shows which boxes you were near. That is the only thing the app puts on the wire about you, and it is stated that way in the Play form below.
- Only while you are there
- The monthly sync in the background carries no location. For such a country it fetches only the one part the publisher sets aside for that case, and that part is the same for everyone. Boxes near you are requested only when you press sync in the app yourself. If the device holds no recent location at that moment, the app asks it for a reading. If you gave the app no location permission there is no point even then, and it stays with that one part.
- Size
- With
If-None-Match, so a check that finds nothing new costs a few hundred bytes. Not for a country in parts: there the question is not whether the country changed but which parts this phone needs now, which such a check cannot answer. - Verification
- ECDSA P-256 signature with SHA-256, checked before anything is parsed. An older version is rejected; the previous file stays in place until the new one parses cleanly.
- Default
- On, with one switch to turn it off. Off, the bundled list keeps working.
The honest limit. The file lives on GitHub, so that download reveals your IP address to GitHub, like any other download. If you would rather not, switch the sync off; the bundled list keeps working.
2. Looking up an address for a shop you pin yourself
Only on a button press
- When
- Only when you press the "find the address" button. Never on its own, never in the background.
- What
- One reverse lookup of the coordinates you just captured, through Android's own Geocoder. On most devices that question goes to Google, with those coordinates attached. That is stated on the button.
- Required
- Never. An address is decoration; coordinates are what makes the alert work.
- If it fails
- Nothing. The field stays empty, the pinned shop works fine.
3. Refreshing the brand list
On by default
- When
- In the same background run as the store list, so at most once a month, and on unmetered wifi unless you allow mobile data. Never as a run of its own, and never at all if you have switched the store list sync off.
- What
- One
GETto one fixed, public address onraw.githubusercontent.com, unauthenticated. No account, no device id, no location, no card data. The address is the same for everyone, so nothing can be read out of it beyond the fact that a Stapel is running. The request does name the app and its version, exactly as the store list one does. - What comes back
- The brand list: names, colours and icons for the chains the app knows. No shop addresses and no images. The logos come separately, below.
- Why
- A chain that changes its name used to stay wrong until a new version reached the Play Store. This way the name on your card is right sooner.
- Verification
- The same ECDSA P-256 signature with SHA-256, checked before anything is read. An older list is refused, and the bundled list stays as the floor.
- Default
- On, behind the same switch as the store list. There is no separate switch, because there is no separate run.
4. Updating the brand logos
On by default
- When
- In the same background run as the store list, so at most once a month, and on unmetered wifi unless you allow mobile data. Never as a run of its own, and never at all if you have switched the store list sync off.
- What
- One
GETfor each country you switched on whose logos did not come with the app, to a fixed, public address onraw.githubusercontent.com, unauthenticated. No account, no device id, no location, no card data. The country is in the address, so the requests do show which countries you switched on. The store list above already shows that in more detail. - What comes back
- The logo images for the chains of that country, and nothing else. No shop addresses, no text about you.
- Why
- Otherwise every phone carries the logos of every country, and most of that is for countries you never shop in.
- Verification
- The same ECDSA P-256 signature with SHA-256, checked before anything is read. After that each logo is checked separately against the signed index before it is written.
- If it fails
- Nothing. The card draws the symbol for the kind of shop instead, which is what about a third of the brands already do.
- Default
- On, behind the same switch as the store list. There is no separate switch, because there is no separate run.
Card data sent0
There is no fifth. No analytics SDK, no crash reporting sent anywhere, no advertising id, no Firebase, no Google Wallet, no fused location, no A/B testing service, no font from someone else's server.
Permissions, and why each one
Seven are asked for by Stapel itself. Android's own screen shows twelve, and that difference is not a mistake: five come from libraries the app ships with.
WorkManager, the background scheduler from Google that arrival detection uses, adds three that never ask you anything and that you cannot refuse: network state, staying awake for as long as a task runs, and being allowed to run that task. The unlock behind a closed card adds two more, both from Android's own biometric library: using a biometric, and the older fingerprint permission that library still carries. A test in the source code checks both lists, so this page cannot quietly go stale.
| Permission | What for | Required |
|---|---|---|
CAMERA |
Scanning a barcode. No image is stored or sent | No, only for scanning |
INTERNET |
The calls above and nothing else | Yes, but the sync can be switched off |
ACCESS_COARSE_LOCATION |
Which shop is nearby, only while the app is open | No, only for arrival detection |
ACCESS_FINE_LOCATION |
Accurate enough to tell a shop from its neighbour | No, only for arrival detection |
ACCESS_BACKGROUND_LOCATION |
Putting the card up while the app is closed. Only after a separate screen explaining the cost | No, off by default |
POST_NOTIFICATIONS |
The notification when you arrive at a shop | No |
RECEIVE_BOOT_COMPLETED |
Re-arming proximity alerts after the device restarts | No, only for background arrival detection |
| Permission | Where it comes from | What for |
|---|---|---|
ACCESS_NETWORK_STATE |
WorkManager | Checking for Wi-Fi before a scheduled task runs |
WAKE_LOCK |
WorkManager | Keeping the device awake for as long as such a task runs |
FOREGROUND_SERVICE |
WorkManager | Being allowed to run that task |
USE_BIOMETRIC |
Android's biometric library | The unlock for a card you closed |
USE_FINGERPRINT |
Android's biometric library | Nothing any more. It is the old permission from before
USE_BIOMETRIC, which that library still carries |
Location
Arrival detection is off by default. On, the app hands Google Play Services geofencing the coordinates of the nearest stores it has room for, plus one circle around wherever you are at that moment. That circle is sized to the stores it just handed over: it reaches the furthest of them, and a little past it, so that store's own circle sits inside. How wide that comes out depends on how many cards you carry and how close together the stores are. Measured against the stores the app ships: with a handful of cards it is around a kilometre in the busiest city centres and several kilometres across most of the country. It is never smaller than 500 metres, and where there was no store to hand over at all it is three kilometres. Both grow by up to one and a half kilometres more when the position the circle was drawn around is no longer fresh, which covers the ground you could have walked since it was taken. The app searches a box twelve kilometres in each direction, so a store near a corner of that box puts the circle just over eighteen and a half kilometres out, that margin included. Leave that circle and the app works out the nearest stores again. Play Services taps the app when you enter a circle. No location history, no trail, and Stapel sends no coordinate anywhere. The only exception is the button in section 2.
The honest limit: that geofencing is Google code running on your device, and it gets to see where you are. What Google Play Services does with that is settled in Android's own location settings, not in Stapel. For the position itself the app still asks Android directly rather than fused location. Leave arrival detection off and the app registers no geofence at all.
That notification comes with a second switch, off by default: the card on your lock screen. On, an arrival puts the barcode in the notification itself, and a tap opens the card with its code and number without the phone being unlocked. That is useful at a till, and it also means anyone who picks your phone up can see that one card. Off, the card only opens after you unlock, and Stapel asks Android to redact the notification on the lock screen so it does not name the shop you walked into. Android keeps a notification channel on the setting it was created with, so an install from before this version keeps what it had. A test in the source fails if that default is ever flipped.
Every shop in the list carries a Show on map button. It hands two things to
whichever map app you have set as your own default, through the geo: address
every Android map app understands: the coordinate of that shop, and the name that
shop is listed under. For a shop from the catalogue that name comes from OpenStreetMap and is
already public. For a shop you pinned yourself it is the text you typed when you pinned it,
so that text is what the map app receives. Your own position is not part of it, and Stapel
does not pick the map app for you. What that app then does with it falls under that app's
privacy policy. With no map app installed, the button is not there.
Handing a card over to another phone
Two routes, both started by you, both without a server belonging to anyone.
- As a QR code
- The app draws the card as a QR code on your screen, and the other phone reads it with its camera. In it is what the database holds for that card: the name, the chain, the number itself, the barcode format, and a note, balance text or expiry date if you filled one in. It goes from screen to camera; no network call is involved and I see none of it. If it does not fit in 800 bytes the code gets too dense to read across a table, and the app says so, leaving only the file route.
- As a file
- The same data as a plainly readable JSON file. The app puts it in its own directory and hands it to Android's share sheet; which app gets it after that is your pick. Pick something that sends it to a server and the card number goes along. That is the same trade-off as your own export, and the file is just as readable.
- What does not go through
- A demo card, because it is not yours to pass on and the other phone is offered one of its own in the shop. And a card you closed does not go without you unlocking first: handing it over is showing the code to someone who keeps it, so it costs the same unlock as reading it yourself.
A voucher out of a PDF, or out of a photo
A gift voucher usually arrives as a PDF in your mail. You point at that file with Android's file picker, so the app gets that one file and nothing else from your storage. For an image it works the same way, through the photo picker.
The app draws the pages itself with the PDF reader built into Android and looks for a barcode on each page until it finds one. That happens on the device. The file is read where it sits and not copied, and what comes out is the code, not the image: the app keeps no page and no photo. Nothing goes anywhere.
A password-protected PDF is one the app cannot open, and it says so rather than making something of it.
A card you closed
You can close a card. Its code and number then only come out after Android has had you unlock: a fingerprint or face, or else the phone's PIN, so a device without biometrics is not locked out. The same unlock covers handing it over, and opening it up again.
The recognising is Android's. Stapel sees no fingerprint and no face scan, only whether it succeeded, and keeps nothing of it. One unlock holds until the app goes to the background; after that every closed card is closed again. This is what the two permissions from the biometric library in the list above add.
The honest limit: if the phone can recognise nobody, closing a card would put it out of reach. The app then draws the code anyway and says on the screen that it is not protected, rather than shutting you out of your own card.
Backup: read this bit
Two things can make a copy of your cards, and they are not the same.
- The export you make
- A plainly readable JSON file you save wherever you want. It is not encrypted: anyone holding the file can read your codes. That is the price of an export you can still open in ten years. I never see it.
- Android Auto Backup
- An operating-system feature. If it is on, Android itself puts a copy of the app's data into your Google account, encrypted with your screen lock. Nothing reaches me, but your card numbers do end up in a Google account. Switch it off under Settings → Google → Backup; the app says this on the backup screen too.
Google Play Services is in the app
Google Play Services ships inside the install package, for two things: scanning barcodes on the device itself, and the geofencing behind arrival detection.
- Scanning. The model is in the install package and runs locally. No photo and no code goes to a server. The scanning library cannot be separated from Play Services; that was attempted in every version.
- Geofencing. Only for arrival detection, and that is off by default. What the code gets to see of your location is under Location.
- The app talks to no Google service on its own initiative.
- Nothing beyond that: no Wallet, no analytics, no advertising id, no Firebase service. The scanner does drag a few Firebase libraries along, but those are scaffolding for the scanner itself: there is no Firebase project behind them and nothing goes to one.
- A test fails as soon as a library arrives that is not on the licences screen in the app, so that list cannot quietly go stale.
- So I say "no Google network call, no account, no analytics" rather than "no Google code". It is also why Stapel will not be on F-Droid.
Deleting your data
Uninstalling erases everything: no copy elsewhere, no account to close. Delete your own export yourself, and an Auto Backup copy through your Google account's backup settings.
For the Play data-safety form
This block exists to be copied verbatim into the Play Console. The page is the source; the store listing is the copy.
| Question | Answer |
|---|---|
| Does the app collect user data? | Yes, one kind: approximate location. With a country switched on that is published in parts, the app requests the parts lying near your last known point, each part at its own address. The coordinate itself is not sent, but which addresses are requested shows which parts of the country the device is near. Nothing else |
| Does the app share data with third parties? | Yes, that same approximate location, with the host serving the store files. Sharing here is the same request as collecting: the app keeps none of it and sends it nowhere else |
| Approximate location: purpose | App functionality only, namely fetching that country's store list. No advertising, no analytics, no personalisation, no profile |
| Approximate location: required? | No. The sync can be switched off, and the app works on the bundled list. Without location permission, and on the monthly background sync, the app fetches only the one part that is the same for everyone. It happens only when you press sync in the app |
| Approximate location: is it retained? | Not by me: there is no Stapel server, so no location reaches me. On the device itself one thing does, and it belongs here: per country, the point its parts were selected against, coarsened to a grid cell, so the app can tell you your data is for somewhere else. It goes into your device backup. What the host keeps in its own logs is the host's to decide, as with any other download |
| Is data encrypted in transit? | Yes, as far as anything is transmitted at all. The store list is fetched over HTTPS from one fixed host; everything that can be learned about you along the way is in the approximate-location rows above. Press “look up the address” and the app hands the coordinates you just pinned to Android’s Geocoder; what Android then puts on the wire is Android’s to decide |
| Can users request deletion? | Uninstalling erases everything. Nothing is stored remotely to delete |
| Precise location | Used on the device, optional, off by default. Not collected and not stored off the device. One exception, which you switch on yourself: the “look up the address” button shares that single coordinate with Android’s Geocoder |
| Photos, files and camera | Imagery is processed on the device to read a barcode, whether it comes from the camera, from a photo you point at, or from a PDF. None of it is stored and none of it is transmitted. Hand a card over as a file and that file goes to the app you pick in Android's share sheet; the developer does not get it |
| Personal info, financial info, contacts, messages | None of these are collected |
| Analytics, crash reporting, advertising id | Not present. Google's scanner library wanted to send usage data about your device on its own; that component was removed from the app, and a build check keeps it out |
| Backup note | Android Auto Backup may copy app data into the user's own Google account. That is a platform feature, it can be switched off, and the developer receives nothing |
This website
No cookies, no Google fonts, no CDN, no embedded video, no share buttons, no ad network. One script: Cloudflare Web Analytics, which counts page views. It sets no cookies, reads none, and builds no browser fingerprint. It stores nothing that traces back to you, so there is nothing to consent to and there is no cookie banner.
What it produces is a count: which pages were viewed, which site visitors arrived from, and which country. Never who. Anyone who wants no request to Cloudflare at all can block the script; the site behaves exactly the same, because nothing depends on it.
The hosting provider, like any web server, sees your IP address and user agent in its own logs. I have no access to those.
Your rights under the GDPR
The GDPR gives you the right to access, correct and delete personal data someone processes about you. Here there is nothing to access: I keep no dataset you could appear in, because there is no Stapel server to put one on.
One thing deserves a better answer than "nothing". For a country published in parts, the app shows the host which parts of the country you are near; that is approximate location, and so it is personal data. I do not receive it and do not retain it. If you would rather it did not happen at all, switch the sync off or leave that country off; the bundled list keeps working.
If you find a call that is not described here, I want to hear it through Contribute. A hole in this page is a bug.
When this page changes
Changes appear in the release notes. If the promise narrows, it is narrowed here in the same change that alters the app.