Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

adoption-and-telemetry.md

v0.1.0-build.85 Latest Commit ede38f5

Size
6.7 KB
Lines
136
Language
Markdown
SHA-256
a36e8b0cfbd3…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

Adoption and telemetry

This document is normative for every CKey SDK and for the openckey.org ingestion service. It implements specification §9.

Modes

Value Name Counts locally Sends to openckey.org
0 CKEY_TELEMETRY_OFF no no
1 CKEY_TELEMETRY_ON yes yes, aggregate batches (default)
2 CKEY_TELEMETRY_LOCAL yes never

Every SDK uses these numbers. Each language may also offer named constants. Setting any other value is a configuration error: CKEY_INVALID_ARGUMENT in C, and the language's own error elsewhere. The mode is never silently reset to 1.

Opting out takes one line, for example CKey.telemetry = 0 (Swift, Kotlin, JS) or ckey_telemetry_set_mode(0) (C).

Switching modes

From → to Effect
any → 0 Stops counting. Purges all counters, including unsent ones. Nothing more is sent.
1 → 2 Keeps local totals. Drops the unsent delta, so it can never be sent later. If a batch is in flight, its result is ignored.
2 → 1 Starts a new unsent delta at zero. Counts made while in mode 2 are never sent.
0 → 1 or 2 Starts counting from zero.

Persisting counters across app restarts is a separate host option (ckey_telemetry_export_state / ckey_telemetry_import_state). It is off unless the host stores the exported state. Importing never re-enables sending, and the mode is not part of the state.

What a scan attempt is

In continuous camera mode, the unit is a scan session, not a frame:

  • Starts: when the host starts scanning (ckey_telemetry_session_begin), for example when the camera view appears.
  • Succeeds: at the first valid decode (ckey_telemetry_session_success). Later frames in the same session are not counted.
  • Fails: when the host stops scanning without a decode (ckey_telemetry_session_end), or when 30 seconds pass with no decode. On timeout, the next frame implicitly starts a new session.
  • Frames: processed frames are counted separately (ckey_telemetry_frame), together with their processing time.

Collected fields

Only counts and coarse durations are collected:

  • session counts (started, succeeded, failed);
  • the number of frames processed;
  • the number of decodes by profile;
  • histograms of time to first decode and of per-frame processing time;
  • the SDK name and version;
  • a coarse platform name;
  • the UTC day the batch was cut.

The SDK never collects:

  • payloads, URLs or any decoded content;
  • user, device or installation IDs;
  • advertising IDs or fingerprints;
  • location;
  • IP addresses (beyond what the network itself exposes);
  • per-scan event histories.

Telemetry never runs on the decode path. Recording is a few atomic counter updates. Sending is asynchronous, batched, and never blocks or delays scanning. A failed send keeps the delta for the next attempt, and an offline device simply accumulates counts.

Send cadence

At most one batch per hour per app process, and only when there is something to send. The SDK also tries a send when the app moves to the background. One request carries one batch, so there is never one request per scan.

Batch schema, version 1

POST https://openckey.org/api/telemetry/v1/batch with Content-Type: application/json and a body of at most 4,096 bytes:

{
  "schema": 1,
  "sdk": "swift",
  "sdk_version": "0.1.0-build.61",
  "platform": "ios",
  "day": "2026-11-05",
  "sessions": { "started": 12, "succeeded": 10, "failed": 2 },
  "frames": 3120,
  "decoded": { "p0": 7, "p0_1": 1, "p1": 1, "p2": 1, "p3": 0 },
  "time_to_decode_ms": { "count": 10, "sum": 4210, "buckets": [0, 1, 3, 4, 1, 1, 0, 0, 0, 0] },
  "frame_ms": { "count": 3120, "sum": 40110, "buckets": [10, 400, 2000, 600, 100, 10, 0, 0, 0, 0] }
}

Field rules (the service rejects anything else with 400):

Field Rule
schema exactly 1
sdk one of c, swift, android, web, dotnet
sdk_version matches ^0\.[0-9]+\.[0-9]+(-(build|dev)\.[0-9]{1,6})?$
platform one of ios, macos, android, web, windows, linux, other
day YYYY-MM-DD, at most 2 days in the future and at most 35 days in the past (server clock)
sessions object with exactly started, succeeded and failed
frames integer
decoded object with exactly p0, p0_1, p1, p2 and p3
time_to_decode_ms and frame_ms object with exactly count, sum and buckets (10 integers)
  • Every count is an integer from 0 to 10,000,000. A sum is an integer from 0 to 10<sup>12</sup>.
  • Bucket counts must add up to count.
  • Unknown fields, at any depth, are rejected.

Histogram bucket upper edges, in ms (the last bucket is everything above):

Histogram Edges
time_to_decode_ms 25, 50, 100, 200, 400, 800, 1600, 3200, 6400, ∞
frame_ms 2, 5, 10, 20, 40, 80, 160, 320, 640, ∞

The service replies 204 when it accepts the batch, 400 for an invalid one, 413 if the body is too large, and 429 when rate limited. SDKs drop a batch after a 400 or 413, and keep it after a 429, 5xx or network error.

Service obligations

  • Storage: aggregates only, summed per (day, sdk, sdk_version, platform). Raw request bodies are not stored.
  • IP addresses:
    • not written to application logs or the database;
    • rate limiting uses an in-memory key derived from the IP with a random salt that rotates daily;
    • nothing derived from the IP is persisted.
  • Retention: daily aggregates are kept for 400 days.
  • Abuse limits: per-key rate limits, a per-day cap on accepted batches, and size limits. Counts can still be inflated by a determined sender; see below.
  • Reporting: the public summary (GET /api/telemetry/v1/summary) labels its figures reported by SDKs that left telemetry on. These are not total usage, unique users or worldwide scans.

Integrity caveat

Anonymous counters from open-source SDKs are unauthenticated. They can be spoofed, replayed or inflated, and without identifiers they cannot be deduplicated. Treat the figures as a coarse signal, and publish the methodology alongside them.

Other adoption evidence

These are reported separately and never merged into one "users" number:

  • package downloads (not the same as active installs);
  • GitHub activity;
  • voluntarily registered apps and businesses;
  • opt-in redirect counts from openckey.org short links.

Privacy disclosures

Integrators must disclose telemetry according to their own app-store and legal requirements. For example, Apple's privacy manifest lists "Product Interaction → Analytics, not linked to the user, not used for tracking" while mode 1 is on. Builds for OS vendors can leave the telemetry module out entirely: the scanner library does not depend on it.