Ultra Labs LLC
Privacy Policy
SpikeCamp is an offline-first wildland fire web app from Ultra Labs LLC, a Washington State company. The public sites are spikecamp.app and spikecamp.vercel.app.
This policy describes the information the current product collects, where it goes, and how to ask for a copy or deletion. It is written for the California Consumer Privacy Act, as amended by the CPRA, and for the Washington My Health My Data Act.
Controller: Ultra Labs LLC, Washington, USA.
Privacy contact: ops@spikecamp.app. General contact: ops@spikecamp.app.
1. Who this is for
SpikeCamp is not for children under 13. We do not knowingly collect personal information from anyone under 13. If you believe a child under 13 has an account or a bug report, email ops@spikecamp.app and we will delete what we can identify.
You can use the public fire map, fire pages, evacuation zones, shelters, and safety alerts without an account. An account is for sync, subscriptions, family links, and crew tools.
2. Information we collect
What we hold depends on what you turn on. Items marked “on this device” stay in the browser until you clear them or use Settings → Your data.
Account
If you sign in, Supabase Auth stores an account. You can use Google or an email magic link. We do not ship a Google analytics or advertising SDK.
From Google sign-in we receive the scopes the product requests: openid, email, and profile (name and profile image URL). The session is stored in this browser under the localStorage key spikecamp.auth. Until sign-in is configured, a development build can keep a sample identity in auth.mock.v1 on the device only.
Watched places and alerts
A watched place is a name, a kind (home, work, family, cabin, county, or other), optional county and state, latitude and longitude, and a radius of 5, 10, 25, or 50 miles. Alert preferences (which types, quiet hours) are stored with it. The inbox of notices on the device keeps up to 200 items.
If you are signed in, the app syncs places, preferences, and the Web Push subscription to Supabase (watched_places, alert_prefs, push_subscriptions, alert_deliveries). A delivery row can include a title, body, severity, and a latitude and longitude for the event. Push subscriptions store the browser endpoint and the encryption keys the browser gives us (p256dh and auth). We delete a subscription when you turn push off, or when the push service says the endpoint is gone.
The alerts worker may send a place’s latitude and longitude to the FCC census-block API to fill in county and state. That request does not include your name or account id.
Place search in the alerts screen calls the public Nominatim service (nominatim.openstreetmap.org) with the text you type. That request leaves your device and includes your IP address. Nominatim is an OpenStreetMap service. Its usage policy expects a low rate and an identifiable application.
Location on the map
“Follow my location” uses the browser’s Geolocation API (watchPosition, high accuracy) to move the map. Those fixes are used for the camera. Map follow does not upload a GPS track.
Crew tracking (opt-in, per incident)
Crew tracking is off until you join an incident with a code from command and check the opt-in box (consent version 2026-10-17-track-1). While your status is “tracking,” the phone records a position on the cadence command sets and uploads it to Supabase (track_points): time, latitude and longitude, GPS accuracy, altitude, speed, heading, battery percentage, and which link carried it (cell, Wi‑Fi, satellite, mesh). Positions taken with no signal wait on the phone (IndexedDB spikecamp-track) until a link returns. Rest, Stop, and Demob stop collection. Command can also start or stop tracking for you on that incident.
Command for that incident sees your callsign, resource type, crew, status, latest position, and a three-hour trail. Command can create read-only feed links (GeoJSON, KML, Cursor-on-Target) for its common operating picture. Those feeds carry an incident-specific identifier, never your account id or email. An emergency alert stores the time, your last position, an optional note, and when command acknowledged it. An audit log records joins, status changes, alerts, settings, and feed changes.
Crew tracking also carries incident comms. Briefings from command (title, text, operational period, an optional link) are stored with the incident, along with when your phone received each one and when you acknowledged it. Messages you send (text up to 1,000 characters, the channel, and the time) are visible to the people in that channel: everyone on the incident for command announcements, your crew and command for a crew channel, you and command for your command line, or you and one other member for a direct message. The server stores when you last read each conversation so others can see that a message was seen. Messages and briefings are not private correspondence: they are kept as part of the incident record, may be processed by automated systems (for example safety screening, retention, and audit), and may be reviewed by incident command or the agency that owns the incident. Direct messages are shown in the app only to the two people in them, but they are stored on our servers and are subject to the same review. If you turn on notifications, your browser’s push subscription (endpoint and keys) is stored so briefings and messages can reach this phone. Briefings and messages are deleted with the incident’s positions at the end of its retention period.
If you bind a Meshtastic radio, its node id is stored with your incident membership, and position packets from that radio that reach our server through a mesh gateway are stored the same way.
If command turns on tactical replay for an incident, command can replay positions and alerts from the last 14 days of that incident. Each replay is logged. Positions and alerts are deleted automatically after the incident’s retention period (default 30 days, set by command). Audit entries are deleted after 365 days.
“Use my location” when you add a place or finish setup reads one position and, if you save it, stores it as a watched place.
The fleet screen in this build shows sample units. It is not the crew tracker. The on-device outbox has a slot for a position report, and the sync function is still a stub: those queued items are not sent to a server.
Family links do not have latitude or longitude columns. When you send a check-in, the phone reads one position and asks Nominatim (nominatim.openstreetmap.org) for the town, county, and state, using a coordinate rounded to about 1 km. The server stores only the town, county, or region label, plus the check-in text. If there is no fix or no signal, the check-in goes out without an area. Viewers do not get an exact coordinate or a track.
Pins, photos, and video
Crew and incident pins are not public. A pin can include a kind, a category (including a “medic” request), a note of up to 140 characters, a display name, a crew or incident id, and a latitude and longitude. Heads-up pins are written to expire after 12 hours. Requests expire after 24 hours. A lead can extend a heads-up by 12 hours.
Photos are re-encoded on the device. That drops EXIF, including GPS in the file. Limits in code: up to 3 photos, long edge about 1280 px, about 300 KB each, or one video up to about 15 seconds and 3 MB. Media uploads to a private Supabase bucket (pin-media) when you are online. The browser may queue them in the IndexedDB database spikecamp-pins.
Family link
A Pro feature. We store a label, a precision choice (town, county, or region), a night-pause schedule, an optional PIN as a hash (not the PIN itself), a view count, an expiry time, and check-ins (a kind and up to 140 characters, plus the area label). The link token is stored as a hash. People who open the link do not need an account. We do not keep a list of who viewed it beyond the count and a rate-limit bucket. This build does not set a PIN cookie.
Bug reports and client errors
A report can include a category, a description (up to 2,000 characters), an optional email, the route, a diagnostics blob, and an optional JPEG screenshot at or under 300 KB. Diagnostics are the last 40 route changes, script errors, unhandled rejections, and failed fetches. The client strips email addresses, bearer tokens, JWTs, and common secret query parameters before the report is stored. A random client_key in localStorage ties anonymous reports together for the rate limit (5 reports an hour). Signed-in reports also store your user id.
Screenshots go to a private bucket (bug-screenshots). You cannot read that object back. There is no client delete policy on bug_reports.
Separately, the app samples about one in five new error fingerprints per session and calls log_client_error. That row is a fingerprint, a scrubbed message (up to 300 characters), and a route. It is not tied to your user id. Duplicate fingerprints from the last hour are dropped. There is no automatic purge of older rows.
Offline reports wait in the IndexedDB database spikecamp-bugs.
Payments
Pro is $24.99 per year. Crew is $99 per seat per year. Checkout and the billing portal are Stripe. Card numbers are entered on Stripe’s pages. SpikeCamp stores a Stripe customer id, a subscription id, the plan, the seat count, and the subscription status. We do not store full card numbers. The card statement descriptor in the product is SPIKECAMP.
Job posts, if you buy them, are separate products in the app: Standard $79 for 30 days, Featured $149 for 30 days, and a 10-pack at $399. Crew pricing in the product includes a discount and included posts. Those charges also go through Stripe when checkout is wired.
Shop carts and order stubs are stored on this device (IndexedDB crew-app). Partner links can pass through a redirect that counts a click on a product id. That redirect is not a profile of you. “Sold by us” payments are handled by Stripe.
Quals, time, community, and jobs on the device
The quals wallet, licenses, certifications (including an optional photo), and time-report lines are stored in IndexedDB crew-app. A fitness pack-test level, if you type one, sits there. The community screen in this build is a local mock in that same database, including sample posts. Saved job searches are in localStorage. Applying for a job leaves SpikeCamp for the employer’s site (USAJOBS or the agency posting).
Health log on the device
On Pro, Crew, or Agency, Health can store an optional heart-rate reading from a Bluetooth strap and a manual log (heart rate, perceived exertion, an optional oxygen number, a symptom checklist, and a note of up to 140 characters). The record is localStorage key spikecamp.health.v1. It is a different key from map location, fleet check-ins, and watched places.
This build does not upload that key. It does not add it to the family link, a pin, or a command picture. Rest pauses new readings and keeps the log. Demob, or Delete health data, removes the readings from the phone. Settings → Your data also removes the key, because that action clears localStorage.
A separate opt-in is required before a strap connection or a manual entry. The consent record sits in the same key, with version 2026-10-17-health-1, the assignment name you typed, and a timestamp. Changing the assignment name drops that consent until you opt in again. Revoke stops collection. Remind-me checkboxes for Apple Health, Health Connect, Fitbit, Garmin, and an agency chest strap are preferences on this phone. They do not email you and they do not connect a watch.
What we do not collect
This build does not include a third-party analytics or advertising SDK (no Google Analytics, Plausible, PostHog, Mixpanel, or Sentry). We do not sell personal information. We do not use personal information for cross-context behavioral advertising. We do not buy data broker lists.
The service worker (workbox) can precache the app shell. A workbox Google Analytics package is present in the dependency tree. The app does not initialize it.
3. How we use information
We use information to:
- Show fires, perimeters, smoke, air quality, evacuation zones, shelters, weather, temporary flight restrictions, and aircraft positions.
- Send the alerts you asked for, including Web Push.
- Run accounts, subscriptions, and the billing portal.
- Show family the limited status you chose to share.
- Show pins and media to the crew or incident audience you selected.
- Fix defects, using bug reports and sampled error logs.
- Enforce plan limits, rate limits, and abuse rules.
- Comply with law and respond to privacy requests.
We do not use watched-place locations or GPS follow to infer a health condition, and we do not geofence health-care facilities.
4. Sources and service providers
These parties process data or receive a technical request because a feature is on. They are not all “processors” of your account. Map and feed requests often carry an IP address and, for tiles or a bounding box, a rough view of where the map is looking.
| Party | Role in this build |
|---|---|
| Supabase | Auth, database, file storage, edge functions. Region: . |
| Vercel | Hosting, edge routes, and the WA DNR cron. The production alias includes spikecamp.vercel.app. |
| Stripe | Subscription and shop checkout. Card data stays with Stripe. |
| Sign-in only, if you choose Google. Not analytics. | |
| OpenFreeMap | Basemap tiles and fonts. Requests include IP and tile coordinates (an approximate viewport). Basemap data includes OpenStreetMap, ODbL. |
| Nominatim (OpenStreetMap) | Place search text from the alerts screen. |
| FCC | County lookup from a watched place’s coordinates. |
| NIFC / WFIGS (IRWIN-derived) | Public fire locations and perimeters. We query the public service. We do not send your account. |
| NASA FIRMS | Hotspot ingest with our server key. |
| EPA AirNow | Air-quality ingest with our server key. Readings are preliminary. |
| NWS / NOAA | Alerts and weather. The user-agent identifies SpikeCamp. |
| adsb.lol | Aircraft positions for the tactical map when the live proxy is on. Crowdsourced ADS-B under the ODbL. The map attributes “© adsb.lol contributors (ODbL)”. |
| FAA | Temporary flight restrictions. |
| USAJOBS | Public job listings. The ingest sends our API key and the registered contact email, not your account. |
| WA DNR | Public job listings on a schedule. |
| WildCAD / WildWeb | Public dispatch incident lists. We do not store incident-commander names or street addresses from that feed. |
| CAL FIRE, Oregon OEM, county GIS, FEMA NSS | Public evacuation zones and open shelters. |
| Email delivery | Magic-link mail. The current sender is Supabase’s built-in mail until a provider is configured. Provider: . |
| Amazon | If a Shop link is an Amazon Associates link, Amazon handles that visit under its own policy. |
Meshtastic is not a SpikeCamp processor. SpikeCamp does not open a radio. Traffic between your radio and other radios stays on your radio network. If you bind a radio for crew tracking, position packets that a mesh gateway forwards to SpikeCamp are handled as crew tracking positions (see above). The fleet screen’s “Meshtastic” label is sample copy.
5. Retention
These periods are what the code does today. Where the code is silent, we say so.
| Data | What the code does |
|---|---|
| NASA FIRMS hotspots | Rows older than 72 hours are deleted. The public map reads about 48 hours. |
| AirNow readings | Kept about 6 hours. |
| WildCAD incidents that have left the board | Deleted after 3 days. |
| Fire history | Pruned to about 90 days and about 40 points per fire. |
| Pin text | Heads-up expires after 12 hours (extendable by 12). Requests expire after 24 hours. |
| Pin media on Free and Pro | Deleted about 30 days after the pin expires, unless the pin was archived. |
| Pin media on Crew and Agency | The cleanup job skips archived pins. They stay until someone deletes them. There is no further automatic purge. |
| Family links | Hidden after expires_at. A demob milestone sets expiry about 48 hours out. Revoke deletes the row. |
| Push subscriptions | Deleted on unsubscribe, on a gone endpoint, or if the auth user is deleted (cascade). |
| Watched places, preferences, deliveries, billing rows, family check-ins | Deleted if the auth user is deleted (cascade). No separate time limit on deliveries. |
| Bug reports | No automatic deletion. Deleting the auth user sets user_id to null and leaves the report body and any screenshot. |
| Client error logs | No user id and no automatic deletion. |
| Stripe | Stripe keeps payment records under its own policy. Deleting our database row does not by itself close the Stripe customer. |
Health log (spikecamp.health.v1) | On this phone until demob, Delete health data, or a device wipe. Not uploaded. Rest keeps the log and pauses new readings. |
| On this device | Until you clear site data, use Settings → Your data, or the browser evicts storage. |
6. Cookies, local storage, and offline copies
SpikeCamp does not set a tracking cookie and does not run a cookie banner because there is no advertising cookie. Sign-in uses localStorage, not a first-party auth cookie, in this build.
localStorage also holds theme, map layers, recent searches, onboarding, the terms and safety acceptance (spikecamp.legal, with the version string and a timestamp), alert places, a push subscription copy, the bug-report client key, shop state, the health log (spikecamp.health.v1, separate from location), and similar preferences.
IndexedDB databases: crew-app (quals, time, community mock, shop, outbox), spikecamp-pins, and spikecamp-bugs.
The Cache API holds offline map packs (map-pack-…) and the service worker’s app-shell cache. Older map-pack caches are deleted when the pack name changes.
7. Sale, sharing, and California rights
We do not sell personal information. We do not share it for cross-context behavioral advertising. We do not use sensitive personal information (precise location, account credentials, the contents of a message you send us) for purposes that the CPRA treats as advertising. Precise location is used to provide alerts, maps, and pins that you turned on.
If that stays true, a “Do Not Sell or Share” opt-out has nothing to turn off. This section is the disclosure. If we ever sell or share, we will add a working opt-out and update this policy before we do it.
Whether or not Ultra Labs meets the CCPA revenue or volume thresholds, you can ask us to:
- Tell you the categories and specific pieces of personal information we have about you.
- Correct inaccurate information.
- Delete personal information, subject to the exceptions the law allows (for example, security logs and records we must keep).
- Opt out of sale or sharing. Our answer today is that we do not sell or share.
- Limit the use of sensitive personal information. Our use is the feature you turned on, not advertising.
- Not be discriminated against for exercising these rights. Safety alerts stay free either way.
- Appeal a denial. Email ops@spikecamp.app and we will review it.
We will need enough information to find the account. We will not ask you to pay a fee unless a request is manifestly unfounded or excessive. An authorized agent may submit a request with proof of authority.
Categories we may hold, depending on what you use: identifiers (name, email, account id); commercial information (plan, seats, shop order stubs); precise geolocation (watched places, pins, one-time GPS fixes you save); internet or network activity (routes and scrubbed errors in a bug report or client log); user content (notes, photos, video, check-ins); professional information you type into quals or time reports; audio and visual information in pin media or a screenshot.
We do not collect government id numbers, and we do not intend to collect protected-class data. A free-text note could include anything you type.
8. Washington consumer health data
SpikeCamp is not a health-care provider, a health plan, or a medical device. The optional health log is a general-wellness note on the phone. A heart-rate number is not a diagnosis, and a local threshold banner does not call a medic.
We do not ask for diagnoses, medications, reproductive or sexual-health information, or biometric identifiers for advertising. After you opt in for an assignment, the health log can hold a heart rate, a typed oxygen number, exertion, and symptoms you check. That record stays on the device.
Some fields could be “consumer health data” under the Washington My Health My Data Act, and medical information under the California Confidentiality of Medical Information Act, if they identify you and relate to health:
- The health log, when the assignment name or the phone ties it to you.
- A quals fitness or pack-test entry you type.
- A pin category of “medic,” or a note, family check-in, or bug report in which you describe an injury or a health condition.
- Any other free text you choose to put health information into.
We do not use location to infer health status. Air-quality and smoke layers are environmental measurements, not a health record about you. The health log is not mixed into the location store.
We do not sell consumer health data. We do not use it for advertising. We do not geofence health-care facilities. We do not knowingly collect consumer health data from anyone under 18.
Health shows a separate consent screen before a strap connection or a manual log. You can revoke that consent on the Health screen. Demob deletes the readings on the phone. You can also delete the on-device log there without deleting the rest of the app. Server email requests still go to ops@spikecamp.app. There is no server copy of the health log in this build.
9. Deletion and export
Settings → Your data can:
- Download a JSON file of what this browser has stored (credentials such as the sign-in session are left out of the file).
- Delete data on this device, including the local databases and cached map packs, and sign you out.
- Copy a request for a server-side export or deletion. There is no button that deletes the Supabase auth user. Email that request to ops@spikecamp.app.
Deleting the auth user, once we do it, cascades watched places, alert preferences, push subscriptions, alert deliveries, family links, family check-ins, pins, and billing rows. It does not, by itself, delete bug-report bodies, bug screenshots, pin files left in storage, client error rows, or the Stripe customer. Those need a separate step. We will describe that step in the reply to your request. Target time to respond: 45 days.
10. Security
Traffic uses HTTPS. Database tables for accounts, places, pins, family links, and billing are locked with row-level security. Pin and bug-screenshot buckets are private. No method of transmission or storage is perfectly secure.
11. Children
The service is not directed to children under 13, and we do not knowingly collect their personal information. Safety content is written for adults and for families using a link a firefighter chose to share.
12. Changes
13. Contact
Ultra Labs LLC, . Privacy: ops@spikecamp.app. General: ops@spikecamp.app.