Skip to content

Contribution Graph

The contribution graph is a GitHub-style heatmap that visualizes your daily bit completions. Each cell represents a day, with intensity levels (0-4) computed from completion counts. Use it to visualize execution consistency, track streaks, and prove productive output.

Endpoints

MethodPathDescriptionAuthRate Limit
GET/contribution-graphGet contribution graph dataJWT60/min

GET /contribution-graph

Returns daily completion data with intensity levels and streak information. Defaults to the last 365 days. Filter by chainy or chain for scoped graphs.

Authentication: JWT Bearer token required. Rate limit: 60/min

Request

Query Parameters:

ParamTypeRequiredDefaultDescription
fromstringNo365 days agoStart date
tostringNoTodayEnd date
chainyIdstringNo--Filter to chains within a chainy
chainIdstringNo--Filter to a single chain

If both chainyId and chainId are provided, chainId takes precedence.

Response

Response Example
json
{
  "data": {
    "cells": [
      { "date": "2026-03-15", "count": 0, "level": 0 },
      { "date": "2026-03-16", "count": 3, "level": 2 },
      { "date": "2026-03-17", "count": 5, "level": 4 }
    ],
    "streaks": {
      "current": 2,
      "longest": 14
    },
    "total": 87
  }
}
Response Fields
FieldTypeDescription
cellsarrayOne entry per day in the date range
cells[].datestringDate in YYYY-MM-DD format
cells[].countnumberNumber of bit completions on this day
cells[].levelnumberIntensity level (0-4)
streaksobjectStreak information
streaks.currentnumberCurrent consecutive-day streak
streaks.longestnumberLongest streak in the queried range
totalnumberTotal completions across the entire range

Code Examples

bash
curl "https://api.chainabit.com/api/v1/contribution-graph?from=2026-01-01&to=2026-03-17" \
  -H "Authorization: Bearer $TOKEN"
javascript
const res = await fetch(
  `${BASE_URL}/contribution-graph?from=2026-01-01&to=2026-03-17`,
  { headers: { Authorization: `Bearer ${TOKEN}` } }
);
const { data } = await res.json();
python
import requests

res = requests.get(
    f"{BASE_URL}/contribution-graph",
    params={"from": "2026-01-01", "to": "2026-03-17"},
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]
Chainy-Scoped Example

Use the id from Create Chainy's response as $CHAINY_ID.

bash
curl "https://api.chainabit.com/api/v1/contribution-graph?chainyId=$CHAINY_ID" \
  -H "Authorization: Bearer $TOKEN"
javascript
const CHAINY_ID = process.env.CHAINY_ID; // from Create Chainy's response

const res = await fetch(
  `${BASE_URL}/contribution-graph?chainyId=${CHAINY_ID}`,
  { headers: { Authorization: `Bearer ${TOKEN}` } }
);
const { data } = await res.json();
python
import requests, os

chainy_id = os.environ["CHAINY_ID"]  # from Create Chainy's response

res = requests.get(
    f"{BASE_URL}/contribution-graph",
    params={"chainyId": chainy_id},
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]

Level Semantics

Levels use quartile-based thresholds relative to the user's peak activity in the queried range:

LevelMeaningThreshold
0No activitycount === 0
1Light activitycount <= 25% of peak
2Moderate activitycount <= 50% of peak
3High activitycount <= 75% of peak
4Peak activitycount > 75% of peak

Levels are always relative to the user's own maximum daily count in the range. A user averaging 2 completions/day and a user averaging 20 completions/day both see the full 0-4 range based on their own data.


Scoping Summary

Query ParamScopeDescription
AccountAll completions for the authenticated user
chainyIdChainyCompletions for chains within the specified chainy
chainIdChainCompletions for a single chain

Full Year Example

Request the full year to render a complete heatmap:

bash
curl "https://api.chainabit.com/api/v1/contribution-graph?from=2025-03-17&to=2026-03-17" \
  -H "Authorization: Bearer $TOKEN"
javascript
const from = new Date();
from.setFullYear(from.getFullYear() - 1);

const res = await fetch(
  `${BASE_URL}/contribution-graph?from=${from.toISOString().slice(0, 10)}&to=${new Date().toISOString().slice(0, 10)}`,
  { headers: { Authorization: `Bearer ${TOKEN}` } }
);
const { data } = await res.json();
// data.cells has 365-366 entries
python
import requests
from datetime import datetime, timedelta

today = datetime.utcnow()
one_year_ago = today - timedelta(days=365)

res = requests.get(
    f"{BASE_URL}/contribution-graph",
    params={
        "from": one_year_ago.strftime("%Y-%m-%d"),
        "to": today.strftime("%Y-%m-%d"),
    },
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]
# data["cells"] has 365-366 entries
json
{
  "data": {
    "cells": [
      { "date": "2025-03-17", "count": 1, "level": 1 },
      { "date": "2025-03-18", "count": 0, "level": 0 },
      "... (365 entries total)",
      { "date": "2026-03-17", "count": 5, "level": 4 }
    ],
    "streaks": {
      "current": 23,
      "longest": 45
    },
    "total": 892
  }
}

Built with purpose.