Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/samgutentag/bcycle-map/llms.txt

Use this file to discover all available pages before exploring further.

The /beacon endpoint accepts lightweight analytics events from the BCycle Map frontend and stores them immutably in Cloudflare R2. Each event is written as an individual JSON object (one PUT per event, no read-modify-write), so concurrent beacons from different users never clobber each other. Events age out after 90 days. A separate internal endpoint (GET /api/insights) aggregates stored events for the analytics dashboard.

Endpoint

POST /api/beacon
Content-Type: application/json

Request body

type
string
default:"pageview"
Event type. One of "pageview" or "event". Any value other than "event" is coerced to "pageview".
path
string
required
The URL path where the event occurred, e.g. / or /flow. Must be between 1 and 200 characters.
name
string
Event name. Required when type is "event"; ignored for pageviews. Must be between 1 and 64 characters. A beacon with type="event" and a missing or invalid name is rejected with 400.
props
object
Optional label bag for the event. Keys and values must be strings. Constraints:
  • Maximum 8 keys (additional keys are silently dropped).
  • Key length: 1–64 characters (invalid keys are skipped).
  • Value length: truncated to 100 characters.
  • Total serialized size capped at 1024 bytes (the whole object is dropped if exceeded).
Ignored for type="pageview".
referrer
string
The document.referrer value at the time of the event. Silently ignored if longer than 500 characters.
session
string
An opaque client-generated session identifier (e.g. a UUID). Silently ignored if longer than 64 characters.
viewport
string
Browser viewport dimensions as "WxH" (e.g. "1440x900"). Silently ignored if longer than 16 characters.

Response

Success — 204 No Content

The event was accepted and stored (or a storage write failure occurred but was logged without surfacing to the caller). The response body is empty.
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *

Error responses

StatusBodyMeaning
400invalid jsonRequest body is not valid JSON.
400invalid pathpath is missing, empty, or longer than 200 characters.
400invalid event nametype is "event" but name is missing, empty, or longer than 64 characters.
405method not allowedRequest method is not POST (or OPTIONS for preflight).

CORS preflight

OPTIONS requests receive a 204 response with:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: content-type
Access-Control-Max-Age: 86400

Storage layout

Each accepted event is stored as a single R2 object:
analytics/<YYYY-MM-DD>/<unix_ts>-<uuid8>.json
For example: analytics/2026-05-13/1747144512-a3f8b21c.json The per-event key format guarantees concurrent writes never collide. Events are retained for 90 days before aging out of the archive.

Example requests

Pageview beacon:
curl -X POST https://bcycle-map-read-api.developer-95b.workers.dev/api/beacon \
  -H "Content-Type: application/json" \
  -d '{
    "type": "pageview",
    "path": "/flow",
    "referrer": "https://bcycle-map.pages.dev/",
    "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "viewport": "1440x900"
  }'
Named event beacon:
curl -X POST https://bcycle-map-read-api.developer-95b.workers.dev/api/beacon \
  -H "Content-Type: application/json" \
  -d '{
    "type": "event",
    "path": "/flow",
    "name": "scrubber_drag",
    "props": {
      "system": "bcycle_santabarbara",
      "window": "24h"
    },
    "session": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }'
Both requests return 204 No Content on success.
Write failures (e.g., transient R2 errors) are logged server-side but do not cause the endpoint to return an error status. The caller always receives 204 as long as the payload was valid, ensuring analytics failures never degrade the user experience.
Stored events include a country field populated from Cloudflare’s cf.country request property. This field is set server-side and is not accepted from the client body — it cannot be spoofed by the caller.

Build docs developers (and LLMs) love