Open Data API

Build on our data

A public, key-less REST API for the community's contribution stats, leaderboard, blog, and showcase. Wire it into your TUI, bot, or dashboard.

No API key

Fully public, read-only open data. Just make the request.

Rate limited

20 requests/min and 500/day per IP, fair for everyone.

Heavily cached

Served from cache and refreshed on every data sync.

OpenAPI + Zod

Typed schemas and a spec you can generate clients from.

Introduction

The Open Data API exposes the same data that powers this website so you can build your own tools on top of it. Everything is read-only, returns JSON, and requires no authentication.

Every successful response is wrapped in an envelope: the payload lives under data, with optional meta (such as count, month, or lastSyncedAt). Errors return { "error": { "code", "message" } }.

Base URL

All endpoints are versioned under a single base URL: https://githubcommunity.az/api/v1

Paths in this documentation are shown relative to it, e.g. /stats means https://githubcommunity.az/api/v1/stats.

Authentication

None. This is open data, so there are no API keys, tokens, or sign-up. Just send a GET request. Requests are attributed to your IP for rate limiting only.

Rate limits

Limits are applied per IP address: 20 requests per minute and 500 requests per day. Exceeding either returns 429 Too Many Requests with a Retry-After header.

Every response includes these headers:

HeaderMeaning
X-RateLimit-LimitPer-minute request cap (20).
X-RateLimit-RemainingRequests left in the current minute.
X-RateLimit-ResetUnix seconds when the minute window resets.
X-RateLimit-Limit-DailyPer-day request cap (500).
X-RateLimit-Remaining-DailyRequests left today.
Retry-AfterSeconds to wait after a 429 response.

Caching

Responses are served from a server-side cache and refreshed automatically whenever the underlying data is synced, so you always get fresh numbers without hammering the database. Each response also carries Cache-Control: public, max-age=60, so your own client may reuse it for up to a minute. Please cache on your side rather than polling in a tight loop.

Errors

Errors use standard HTTP status codes and a consistent body: { "error": { "code", "message" } }.

StatusCodeWhen
400invalid_paramsA path parameter is malformed (e.g. bad year/month).
404not_foundNo resource for that slug or month.
429rate_limitedYou exceeded the per-minute or monthly limit.
500internal_errorSomething went wrong on our side.

Versioning

The API is versioned in the path (/api/v1). Additive changes (new endpoints or fields) ship within v1; any breaking change would land under a new version.

MCP server

For AI assistants (Claude, Cursor, and other Model Context Protocol clients) the same data is available as an MCP server, so a chatbot can query the community directly. It needs no key.

Endpoint: https://githubcommunity.az/api/mcp/mcp. Add it to your client's config:

{
  "mcpServers": {
    "azerbaijan-github-community": {
      "url": "https://githubcommunity.az/api/mcp/mcp"
    }
  }
}

Tools: get_stats, get_leaderboard, get_all_time_leaderboard, get_blog_posts, get_blog_post, and get_showcase_projects.

Endpoints

Every endpoint below has copy-pasteable snippets in 12 languages and a live “Run” button.

Stats

GET/api/v1/stats

Community stats

Aggregated community totals, the same numbers shown on the home page hero.

curl -s "https://githubcommunity.az/api/v1/stats"

Leaderboard

GET/api/v1/leaderboard

Current month

Top 50 contributors for the current month, ranked by commits.

curl -s "https://githubcommunity.az/api/v1/leaderboard"
GET/api/v1/leaderboard/all-time

All-time

Top 50 contributors by all-time commits. Note: GitHub's contribution window means this reflects roughly the last 12 months (the site labels it “Last Year”).

curl -s "https://githubcommunity.az/api/v1/leaderboard/all-time"
GET/api/v1/leaderboard/{year}/{month}

Specific month

Top 50 contributors for a past month. Returns 404 if no data exists for that month.

curl -s "https://githubcommunity.az/api/v1/leaderboard/2026/07"

Blog

GET/api/v1/blog

List posts

All blog posts (metadata only, without the MDX body), newest first.

curl -s "https://githubcommunity.az/api/v1/blog"
GET/api/v1/blog/{slug}

Single post

A single blog post including its raw MDX body. Returns 404 for an unknown slug.

curl -s "https://githubcommunity.az/api/v1/blog/hello-world"

Showcase

GET/api/v1/showcase

List projects

Community showcase projects with live GitHub repo stats, newest first.

curl -s "https://githubcommunity.az/api/v1/showcase"

Schemas & OpenAPI

A machine-readable OpenAPI 3.1 spec is available. Generate a typed client in your language of choice, or import it into Postman/Insomnia:

Using TypeScript? Drop these Zod schemas in to validate and type every response:

import z from "zod";

// Zod schemas describing the JSON shapes returned by the Open Data API.
// These mirror the database columns (dates are serialized to ISO 8601 strings in JSON).
// They are the single source of truth for the generated OpenAPI document and the docs page.

const DateTime = z.iso.datetime().describe("ISO 8601 date-time");

const LeaderboardUserSchema = z.object({
  githubUsername: z.string(),
  name: z.string().describe("The user's display name"),
  image: z.string().describe("URL of the user's avatar image from GitHub"),
});

export const LeaderboardEntrySchema = z.object({
  userId: z.string(),
  commits: z.number().int(),
  pullRequests: z.number().int(),
  issues: z.number().int(),
  reviews: z.number().int(),
  user: LeaderboardUserSchema,
});

export const StatsSchema = z.object({
  totalCommits: z.number().int().describe("Community-wide commit total (home page hero number)"),
  totalPullRequests: z.number().int(),
  totalUsers: z.number().int(),
  lastSyncedAt: DateTime.nullable().describe("When GitHub stats were last synced"),
});

const BlogAuthorSchema = z.object({
  name: z.string(),
  image: z.string(),
});

export const BlogListItemSchema = z.object({
  id: z.string(),
  slug: z.string(),
  title: z.string(),
  description: z.string(),
  tags: z.array(z.string()),
  coverImage: z.string(),
  userId: z.string(),
  readingTime: z.number().int().describe("Estimated reading time in minutes"),
  createdAt: DateTime,
  author: BlogAuthorSchema,
});

export const BlogPostSchema = z.object({
  id: z.string(),
  slug: z.string(),
  title: z.string(),
  description: z.string(),
  tags: z.array(z.string()),
  coverImage: z.string(),
  userId: z.string(),
  contentMdx: z.string().describe("Raw MDX body of the post"),
  readingTime: z.number().int(),
  createdAt: DateTime,
  updatedAt: DateTime,
  author: BlogAuthorSchema.extend({ githubUsername: z.string() }),
});

export const ShowcaseProjectSchema = z.object({
  id: z.string(),
  repo: z.string().describe("owner/name of the GitHub repository"),
  submittedBy: z.string(),
  banner: z.string().nullable(),
  links: z.array(z.string()),
  website: z.string().nullable(),
  createdAt: DateTime,
  stars: z.number().int(),
  forks: z.number().int(),
  openIssues: z.number().int(),
  openPRs: z.number().int(),
  description: z.string().nullable(),
  homepageUrl: z.string().nullable(),
  license: z.string().nullable(),
  language: z.string().nullable(),
  languageColor: z.string().nullable(),
  updatedAt: DateTime,
});

export const ApiErrorSchema = z.object({
  error: z.object({
    code: z.string(),
    message: z.string(),
  }),
});

export type LeaderboardEntryDto = z.infer<typeof LeaderboardEntrySchema>;
export type StatsDto = z.infer<typeof StatsSchema>;
export type BlogListItemDto = z.infer<typeof BlogListItemSchema>;
export type BlogPostDto = z.infer<typeof BlogPostSchema>;
export type ShowcaseProjectDto = z.infer<typeof ShowcaseProjectSchema>;