Manage your subscriptions from your own scripts with a personal API key.
Everything the dashboard does with your subscriptions, email addresses and feed, you can do with the REST API.
On the API keys page of your dashboard, give the key a name (for example
the script that will use it) and an expiry. The key starts with osg_ and is shown only once:
store it somewhere safe, such as a password manager or your CI's secrets.
A key acts as you. If one leaks, revoke it on the same page: anything using it stops working right away.
The API lives under /api on this site. Send the key in the Authorization header:
export OSG_API="https://<this site>/api"
export OSG_KEY="osg_…"
curl "$OSG_API/v1/me" -H "Authorization: Bearer $OSG_KEY"curl "$OSG_API/v1/me/subscriptions" \
-H "Authorization: Bearer $OSG_KEY" \
-H "Content-Type: application/json" \
-d '{ "repo": "freeCodeCamp/freeCodeCamp", "labels": ["help wanted"] }'Only repo is required. The other fields default to:
| Field | Default | Values |
|---|---|---|
labels | [] (every new issue) | Up to 20 label names |
events | Both | issue.opened, issue.labeled |
labelMatch | any | any, all |
channels | Your primary email, right away | [{ "channelId": "…", "frequency": "instant" | "hourly" | "daily" }] |
Get your email addresses' channelIds from GET /v1/me/channels.
Each endpoint has its own page in the API reference,
with its parameters, request and response schemas, and code samples. A Try in Playground button
sends real requests from the page: paste your key in the Authorization field.
| Method | Path | What it does |
|---|---|---|
GET | /v1/me | Your profile and settings |
PATCH | /v1/me | Change timezone, digestHour, streakPeriod, profilePublic, streakReminders or goalEmails |
GET | /v1/me/subscriptions | Your subscriptions |
POST | /v1/me/subscriptions | Follow a repository |
PATCH | /v1/me/subscriptions/{id} | Change labels, events, labelMatch, status (active / paused) or channels |
DELETE | /v1/me/subscriptions/{id} | Delete a subscription |
GET | /v1/me/channels | Your email addresses |
POST | /v1/me/channels | Add an email address (sends a confirmation email) |
POST | /v1/me/channels/{id}/resend-verification | Send the confirmation email again |
PATCH | /v1/me/channels/{id} | Rename an address |
DELETE | /v1/me/channels/{id} | Remove an address |
GET | /v1/me/feed | Issues that matched your subscriptions, newest first |
GET | /v1/me/stats?year= | Open Source Contributions at a glance: total, daily counts, breakdowns, merged pull requests, streaks, years |
GET | /v1/me/contributions?year=&kind=&repo= | Your Open Source Contributions, newest first (paged with cursor) |
GET | /v1/me/contributions/excluded?year= | What wasn't counted, and why |
POST | /v1/me/sync | Refresh your contributions from GitHub (at most every 15 minutes) |
GET | /v1/me/goals | Your goals, with their progress |
POST | /v1/me/goals | Set a goal |
PATCH | /v1/me/goals/{id} | Change a goal's target, visibility or status |
DELETE | /v1/me/goals/{id} | Delete a goal |
GET | /v1/public/profiles/{login}?year= | A public profile (no key needed) |
GET | /v1/public/profiles/{login}/badge.svg | A README badge (no key needed; also at /u/{login}/badge.svg) |
GET | /v1/public/repos/{owner}/{repo}/labels | A repository's labels and open-issue counts (no key needed) |
For security, an API key can't create or revoke API keys, nor delete your account: those need you to be signed in on the website.
GET /v1/me/feed returns up to limit items (1 to 100, default 30) and a nextBefore value.
Pass it as before to get the next page; it's null on the last page.
curl "$OSG_API/v1/me/feed?limit=50" -H "Authorization: Bearer $OSG_KEY"
# { "items": [...], "nextBefore": "2026-10-14T08:12:31.000Z" }
curl "$OSG_API/v1/me/feed?limit=50&before=2026-10-14T08:12:31.000Z" \
-H "Authorization: Bearer $OSG_KEY"Errors come back as JSON with a stable error code and a readable message:
{ "error": "subscription_limit", "message": "You can have up to 50 subscriptions." }| Status | Meaning |
|---|---|
400 | Invalid request (invalid_request, with the details in issues) |
401 | Missing, invalid, expired or revoked key |
404 | Not found, or not yours |
422 | A limit or rule was hit (subscription_limit, goal_limit, no_channel, …) |
429 | Too many requests (rate_limited) |
Up to 300 requests per minute per key. Over that, you get a 429; wait a minute and retry.