SpikeCamp

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.

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:

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.

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.

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:

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:

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:

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.