> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyclemate.club/llms.txt
> Use this file to discover all available pages before exploring further.

# Contribute road data to Cyclemate

> Send road and infrastructure data that feeds our routing algorithm and safe cycling maps

export const ConeGlyph = () => <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
    <path d="M8 2 L11.2 12 H4.8 Z" fill="#FFA500" />
    <path d="M6.2 8.2 h3.6 l-0.55 -1.9 h-2.5 Z" fill="#fff" />
    <rect x="2.6" y="12" width="10.8" height="1.8" rx="0.9" fill="#FFA500" />
  </svg>;

export const FlagBadge = () => <span style={{
  position: 'absolute',
  right: -4,
  bottom: -4,
  width: 14,
  height: 14,
  borderRadius: 999,
  background: '#FF3B30',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center'
}}>
    <svg viewBox="0 0 16 16" width="8" height="8" aria-hidden="true">
      <path d="M4 2 V14" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" />
      <path d="M5 2.4 L13 4.8 L5 7.6 Z" fill="#fff" />
    </svg>
  </span>;

export const ReportInChangesFeed = () => {
  const [tab, setTab] = useState('reports');
  const warning = '#FFA500';
  const danger = '#FF3B30';
  const success = '#1DB954';
  const brand = '#537AEF';
  const crown = '/images/partners/govuk.png';
  const pill = accent => ({
    flexShrink: 0,
    padding: '2px 10px',
    borderRadius: 999,
    fontSize: 12,
    fontWeight: 600,
    color: accent,
    background: accent + '24'
  });
  const tabStyle = active => ({
    padding: '4px 12px',
    borderRadius: 999,
    fontSize: 13,
    fontWeight: 600,
    cursor: 'pointer',
    border: '1px solid ' + (active ? brand : 'rgba(148, 163, 184, 0.5)'),
    background: active ? brand : 'transparent',
    color: active ? '#fff' : 'inherit'
  });
  const eyebrow = {
    fontSize: 11,
    fontWeight: 700,
    letterSpacing: 1,
    opacity: 0.55,
    marginTop: 12
  };
  const rowStyle = {
    display: 'flex',
    alignItems: 'center',
    gap: 10,
    padding: '13px 2px'
  };
  const nameStyle = {
    fontSize: 14,
    fontWeight: 700,
    whiteSpace: 'nowrap',
    overflow: 'hidden',
    textOverflow: 'ellipsis'
  };
  const metaStyle = {
    fontSize: 12.5,
    opacity: 0.65,
    marginTop: 2
  };
  const description = {
    fontSize: 14.5,
    lineHeight: 1.6,
    margin: '20px 0 0'
  };
  const card = 'not-prose rounded-2xl border border-zinc-200 dark:border-zinc-700 px-4 py-2';
  return <div className="not-prose" style={{
    margin: '32px 0'
  }}>
      <div style={{
    display: 'flex',
    gap: 6
  }}>
        <button style={tabStyle(tab === 'reports')} onClick={() => setTab('reports')}>User reports</button>
        <button style={tabStyle(tab === 'closures')} onClick={() => setTab('closures')}>Upcoming closures</button>
      </div>
      {tab === 'reports' ? <div>
          <div style={description}>
            An accepted report lands in the user reports feed, credited to its submitter, with the safety change it caused. For example:
          </div>
          <div className={card} style={{
    marginTop: 16
  }}>
            <div style={eyebrow}>RECENT USER REPORTS</div>
            <div style={rowStyle}>
              <span style={{
    position: 'relative',
    flexShrink: 0,
    width: 28,
    height: 28
  }}>
                <img src={crown} alt="Department for Transport" width="28" height="28" style={{
    borderRadius: 999
  }} />
                <FlagBadge />
              </span>
              <div style={{
    flex: 1,
    minWidth: 0
  }}>
                <div style={nameStyle}>Kingsland Road</div>
                <div style={metaStyle}>London · Reported by Department for Transport · just now</div>
              </div>
              <span style={pill(success)}>Safe</span>
              <span style={{
    opacity: 0.5,
    fontSize: 12
  }}>→</span>
              <span style={pill(warning)}>Heavily avoid</span>
            </div>
          </div>
        </div> : <div>
          <div style={description}>
            A report accepted as a temporary closure appears in the upcoming closures feed with its avoidance window. Routes steer around the road until the window ends. For example:
          </div>
          <div className={card} style={{
    marginTop: 16
  }}>
            <div style={eyebrow}>UPCOMING CLOSURES & ROADWORKS</div>
            <div style={rowStyle}>
              <span style={{
    width: 28,
    height: 28,
    borderRadius: 999,
    flexShrink: 0,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    background: warning + '26'
  }}>
                <ConeGlyph />
              </span>
              <div style={{
    flex: 1,
    minWidth: 0
  }}>
                <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: 6
  }}>
                  <span style={nameStyle}>Kingsland Road</span>
                  <img src={crown} alt="Department for Transport" width="17" height="17" style={{
    borderRadius: 999
  }} />
                </div>
                <div style={metaStyle}>5 Aug – 6 Aug · just now</div>
              </div>
              <span style={pill(warning)}>Heavily avoid</span>
            </div>
          </div>
        </div>}
    </div>;
};

## Overview

We route riders along the safest cycling roads. That safety model is built from data: street networks, collision records, and contributions from organisations that see the road before we do.

There are two ways to contribute:

* **Reports.** A single observation about a road, tied to a coordinate. For example "this street is closed" or "this junction is unsafe". Send them one at a time with [`POST /reports`](/api-reference/reports/submit-report).
* **Data feeds.** A dataset you already publish, which we ingest on a schedule so you contribute continuously without writing any integration code. See [Submit data feed](/api-reference/reports/submit-data-feed).

Both arrive in the same pipeline and feed two things:

* **The routing algorithm.** An accepted contribution changes how routes are computed, steering riders away from the hazard.
* **The safe cycling maps.** The public safety layer at [cyclemate.club/explore/london](https://cyclemate.club/explore/london) reflects accepted findings.

This is the live safe cycling map of London. Roads are coloured by cycling safety, and the layer updates as contributions are accepted:

<iframe src="https://cyclemate.club/embed/explore/london" title="Live safe cycling map of London" loading="lazy" allowFullScreen style={{ width: '100%', height: 480, border: '1px solid rgba(148, 163, 184, 0.4)', borderRadius: 16, marginTop: 8 }} />

<ReportInChangesFeed />

Every contribution is analysed by AI agents and reviewed by humans. Accepted findings feed back into the routing algorithm, typically within minutes. Reports also get a processing summary you can retrieve via [`GET /reports/{id}`](/api-reference/reports/get-report), usually available within 15 minutes of submission.

## What happens to a contribution

An accepted contribution leads to one of three changes.

**A permanent change to a road.** The road's safety rating changes for good, so routes avoid it from then on and its colour changes on the map. We use this when the hazard belongs to the road itself, like a junction that is dangerous by design or a street that is hostile to cycling.

**A temporary avoidance window.** The road is avoided between a start and an end date, then the window expires on its own. We use this for closures, street works, and events. These are the entries in the upcoming closures list above, and it is where most feed items land.

**An algorithm improvement.** Some contributions expose a gap in the model rather than one bad road. We change how a whole class of road is weighted across the city, so a single report can improve thousands of routes at once. These changes affect every route in the city, so they get the most review before they ship.

Not everything changes the map. We close a report without a change when the issue has already been fixed, when we cannot reproduce it, or when the road is outside a city we route in. [`GET /reports/{id}`](/api-reference/reports/get-report) returns either result: `accepted` with a summary of what changed, or `closed` with why nothing did.

## Contributing reports

[`POST /reports`](/api-reference/reports/submit-report) accepts a single report and returns a report ID immediately. Use it to forward events as they happen, like a closure notice landing in your system or a hazard flagged by your users.

Organisations sending reports today include:

* **Department for Transport**, sending street works and closure events
* **Our apps**, sending rider-submitted issue reports from mid-ride

## Contributing data feeds

If you already publish a dataset, we can ingest it directly instead. We take feeds from **TfL Road Disruptions**, **DfT Street Manager**, and **OpenStreetMap** today.

[Submit data feed](/api-reference/reports/submit-data-feed) covers what we ingest, how ingestion works, and what a feed needs before we can use it.

## Reading back what happened

[`GET /reports/{id}`](/api-reference/reports/get-report) returns the report's processing status and, once analysis completes, a one-line summary of the outcome. Callers can only read reports they submitted.

## Coverage

Contributions are accepted for any coordinate. Those inside an operating city can influence routing and the safety maps. Those elsewhere are stored and inform future coverage.

## Getting access

Reports use the same API keys as the rest of the platform. See [Authentication](/api-reference/introduction#authentication). Partner keys are issued with an agreed `source` identifier that attributes your submissions. Data feeds need no key, since we ingest them from your side. [Contact us](https://cyclemate.club/for-developers) to set one up.
