Specification

Copper Roadmap Format

Expose your product roadmap as a machine-readable API. Copper Analytics fetches and displays roadmaps from any site that follows this spec.

Overview

The Copper Roadmap Format is a convention for publishing product roadmaps as structured data. It has two parts:

  1. Markdown files with YAML frontmatter — placed in a ./roadmaps/ directory in your project
  2. A JSON API endpoint at GET /api/copper that reads those files and returns structured JSON using the Copper Data API envelope format

Copper Analytics periodically fetches your /api/copper endpoint and displays your roadmap items alongside analytics data in the dashboard.

Quick Start

Three steps to expose your roadmap:

1

Create a roadmap file

Add a Markdown file to ./roadmaps/ in your project root.

roadmaps/2026-03-10-dark-mode.md
---
title: "Dark Mode Support"
status: in-progress
priority: high
phase: 2
date: 2026-03-10
tags: [ui, accessibility]
---

# Dark Mode Support

Add system-aware dark mode to all pages.

## Tasks

- [ ] Add theme toggle to header
- [ ] Update all components with dark variants
- [x] Add CSS custom properties for theme colors
2

Add the API route

Create an endpoint that reads the files and returns JSON. See reference implementations below.

3

Verify it works

Visit https://yoursite.com/api/copper — you should see JSON output.Copper Analytics will start fetching it automatically.

Markdown Format

Each roadmap item is a single .md file in the ./roadmaps/ directory. The filename becomes the slug (e.g. 2026-03-10-dark-mode.md → slug 2026-03-10-dark-mode).

We recommend prefixing filenames with a date in YYYY-MM-DD- format for natural chronological sorting.

Each file must start with a YAML frontmatter block delimited by ---. The Markdown body after the frontmatter is the full description.

Frontmatter Fields

FieldTypeRequiredDescription
titlestringYesHuman-readable title of the roadmap item
statusenumYesOne of: proposed in-progress completed paused
datestringYesISO date (YYYY-MM-DD). Falls back to filename prefix if omitted.
priorityenumNoOne of: low medium high critical
phasenumberNoNumeric phase/milestone grouping (e.g. 1, 2, 3)
tagsstring[]NoArray of free-form labels for categorization
Complete frontmatter example
---
title: "User Authentication Overhaul"
status: in-progress
priority: critical
phase: 1
date: 2026-04-01
tags: [security, auth, backend]
---

Your markdown description goes here...

API Endpoint

Your site must serve a GET /api/copper endpoint that returns a JSON response using the Copper Data API envelope format, with roadmap items in the modules.roadmaps array.

GET/api/copper

Returns site data including roadmap items via the Copper Data API envelope. Authenticated with a read-only token.

Requirements

  • Must return Content-Type: application/json
  • Must return HTTP 200 with the response body even if there are zero items
  • Should respond within 5 seconds (Copper applies a 5s timeout per site)
  • Should support CORS if you want the endpoint callable from browsers

Response Schema

TypeScript types
interface TaskStats {
  total: number;       // Total task list items
  completed: number;   // Checked items (- [x])
  remaining: number;   // Unchecked items (- [ ])
  percent: number;     // Completion percentage (0-100)
}

interface RoadmapItem {
  slug: string;        // Filename without .md extension
  title: string;       // From frontmatter
  status: 'proposed' | 'in-progress' | 'completed' | 'paused';
  date: string;        // ISO date string (YYYY-MM-DD)
  priority?: 'low' | 'medium' | 'high' | 'critical';
  phase?: number;
  tags?: string[];
  description: string; // Full markdown body
  summary: string;     // First 500 chars of description
  tasks: TaskStats;    // Parsed from markdown task lists
}

// Copper Data API envelope (GET /api/copper)
interface CopperDataResponse {
  site: string;        // Your site's domain or identifier
  fetchedAt: string;   // ISO timestamp of when this response was generated
  version: 1;          // API version
  modules: {
    roadmaps?: RoadmapItem[];
    // future modules: blog?, changelog?, etc.
  };
}
Example response
{
  "site": "myapp.com",
  "fetchedAt": "2026-03-10T14:30:00.000Z",
  "version": 1,
  "modules": {
    "roadmaps": [
      {
        "slug": "2026-03-10-dark-mode",
        "title": "Dark Mode Support",
        "status": "in-progress",
        "date": "2026-03-10",
        "priority": "high",
        "phase": 2,
        "tags": ["ui", "accessibility"],
        "description": "# Dark Mode Support\n\nAdd system-aware dark mode...",
        "summary": "# Dark Mode Support\n\nAdd system-aware dark mode..."
      }
    ]
  }
}

Reference Implementations

Copy-paste one of these into your project. Each reads ./roadmaps/*.md, parses the YAML frontmatter, and returns the JSON response.

Next.js (App Router)

src/app/api/copper/route.ts
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

// pnpm add gray-matter

export async function GET() {
  const dir = path.join(process.cwd(), 'roadmaps');
  const roadmaps = [];

  if (fs.existsSync(dir)) {
    const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
    for (const file of files) {
      const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
      const { data, content } = matter(raw);
      const slug = file.replace(/\.md$/, '');
      const trimmed = content.trim();

      roadmaps.push({
        slug,
        title: data.title || slug,
        status: data.status || 'proposed',
        date: data.date ? String(data.date) : slug.slice(0, 10),
        priority: data.priority || undefined,
        phase: typeof data.phase === 'number' ? data.phase : undefined,
        tags: Array.isArray(data.tags) ? data.tags : undefined,
        description: trimmed,
        summary: trimmed.slice(0, 500),
      });
    }
    roadmaps.sort((a, b) => b.date.localeCompare(a.date));
  }

  return NextResponse.json({
    site: 'yoursite.com',
    fetchedAt: new Date().toISOString(),
    version: 1,
    modules: {
      roadmaps,
    },
  });
}

Express / Node.js

routes/copper.js
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');

// npm install gray-matter

module.exports = function (app) {
  app.get('/api/copper', (req, res) => {
    const dir = path.join(process.cwd(), 'roadmaps');
    const roadmaps = [];

    if (fs.existsSync(dir)) {
      const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
      for (const file of files) {
        const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
        const { data, content } = matter(raw);
        const slug = file.replace(/\.md$/, '');
        const trimmed = content.trim();

        roadmaps.push({
          slug,
          title: data.title || slug,
          status: data.status || 'proposed',
          date: data.date ? String(data.date) : slug.slice(0, 10),
          priority: data.priority || undefined,
          phase: typeof data.phase === 'number' ? data.phase : undefined,
          tags: Array.isArray(data.tags) ? data.tags : undefined,
          description: trimmed,
          summary: trimmed.slice(0, 500),
        });
      }
      roadmaps.sort((a, b) => b.date.localeCompare(a.date));
    }

    res.json({
      site: 'yoursite.com',
      fetchedAt: new Date().toISOString(),
      version: 1,
      modules: {
        roadmaps,
      },
    });
  });
};

Item Status Tracking

Track individual task completion within a roadmap item using standard GitHub Flavored Markdown (GFM) task lists.Copper Analytics automatically parses these checkboxes to calculate completion stats.

Task list syntax
## Implementation Plan

- [x] **1.** Set up project scaffolding
- [x] **2.** Create database schema
- [ ] **3.** Build API endpoints
- [ ] **4.** Add frontend components
- [ ] **5.** Write integration tests

Copper Analytics scans the Markdown body for lines matching - [x] (completed) and - [ ] (incomplete) to compute:

MetricDescription
totalTotal number of task list items
completedNumber of checked items (- [x])
remainingNumber of unchecked items (- [ ])
percentCompletion percentage (0–100), rounded

These stats are included in the API response and displayed as progress indicators on the roadmap detail page.

API response with task stats
{
  "slug": "2026-03-10-dark-mode",
  "title": "Dark Mode Support",
  "status": "in-progress",
  "tasks": {
    "total": 5,
    "completed": 2,
    "remaining": 3,
    "percent": 40
  },
  ...
}

How Copper Analytics Uses It

Once your site exposes /api/copper with a modules.roadmaps array,Copper Analytics will:

1

Fetch your endpoint periodically (every 15 minutes by default)

2

Cache the response in a snapshot collection with a TTL

3

Display roadmap items in your site's dashboard under Planning > Roadmaps, and in the aggregate roadmap view across all your sites

4

Render the full Markdown description on the detail page with GFM support (tables, task lists, code blocks)

No endpoint? No problem.

If your site can't expose an API, you can still create roadmap items manually in the Copper Analytics dashboard. The API integration is optional — it just automates the sync.