API
The app's server function returns one JSON snapshot of a public GitHub account or repository, and makes signed live card links for READMEs. It is free and needs a SkillSafe app token for this app. Guest tokens are refused. Live card images themselves are public.
1. Get a token
Exchange your SkillSafe API key for an app token (valid 30 days).
curl -s -X POST https://api.skillsafe.ai/v1/app-api/authorize \
-H "Authorization: Bearer YOUR_SKILLSAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"slug":"repo-stats-cards"}'
# -> {"ok":true,"data":{"token":"aut_...","expires_at":"..."}}
import requests
r = requests.post("https://api.skillsafe.ai/v1/app-api/authorize",
headers={"Authorization": "Bearer YOUR_SKILLSAFE_API_KEY"},
json={"slug": "repo-stats-cards"})
token = r.json()["data"]["token"]
const r = await fetch("https://api.skillsafe.ai/v1/app-api/authorize", {
method: "POST",
headers: { Authorization: "Bearer YOUR_SKILLSAFE_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ slug: "repo-stats-cards" }),
});
const token = (await r.json()).data.token;
2. Fetch a snapshot
GET https://repo-stats-cards.skillsafe.ai/api/user?login=LOGIN or GET https://repo-stats-cards.skillsafe.ai/api/repo?repo=OWNER/NAME. Add &fresh=1 to skip the server's cache.
- Account snapshot:
login, name, type, bio, company, location, created, followers, following, public_repos, repos_seen,agg(stars, forks, own, forked, archived,langsas{language: [repos, stars, size_kb]},years),repos(the 60 most-starred own repositories),events({t: type, d: day, r: repo, n: commits, a: action}). - Repository snapshot:
full_name, desc, stars, forks, watchers, open_items, open_prs, open_issues, merged_prs, closed_issues, lang, license, topics, created, pushed, archived,langs(bytes),contributors([login, commits, is_bot]),weekly([unix_week, commits], 52 weeks,nullwhile GitHub computes it andweekly_pendingis true),items(newest 200 issues and PRs as[number, is_pr, created, closed, merged, is_open]). - Both carry
fetched_atandrate(GitHub requests left for the server this hour).
curl -s "https://repo-stats-cards.skillsafe.ai/api/repo?repo=pallets/flask" \ -H "Authorization: Bearer YOUR_APP_TOKEN"
import requests
r = requests.get("https://repo-stats-cards.skillsafe.ai/api/user",
params={"login": "torvalds"},
headers={"Authorization": "Bearer " + token})
snap = r.json()
print(snap["agg"]["stars"], snap["followers"])
const res = await fetch("https://repo-stats-cards.skillsafe.ai/api/repo?repo=pallets/flask",
{ headers: { Authorization: "Bearer " + token } });
const snap = await res.json();
console.log(snap.stars, snap.contributors.slice(0, 3));
3. Make a live README card
POST https://repo-stats-cards.skillsafe.ai/api/embed with an app token and a JSON body
{"kind":"user"|"repo","target":"LOGIN"|"OWNER/NAME","card":CARD,"theme":THEME,"options":{"hideBorder":false,"hideTitle":false,"langBy":"repos"}}.
The server checks the account or repository on GitHub and returns {id, url, markdown, html, target, card, theme, refresh_hours}.
cardfor an account:stats,languages,top-repos,activity. For a repository:repo,languages,commits,contributors,flow.theme:auto(default, follows the reader's light or dark setting),light,dark,slate,paper,ocean. Cartoon cards with Pip the mascot:cartoon(sunny in light mode, night in dark),cartoon-sunny,cartoon-night,cartoon-candy,cartoon-forest; Pip's rules are in llms.txt.langBy(repos,stars,size) applies to an account's languages card.urlishttps://repo-stats-cards.skillsafe.ai/api/card/ID.svg. It is public: anyone, including GitHub's image proxy, can load it without a token. Theidis the card settings plus an HMAC signature, so a link cannot be edited into another card.GET /api/card/ID.svgreturnsimage/svg+xmldrawn from live GitHub data, with anupdatedline,Cache-Control: public, max-age=21600and anETag. READMEs show new numbers about every 6 hours. An invalid id returns a small "Invalid card" SVG with status 404. When GitHub fails, the last good copy is served, or a "Stats temporarily unavailable" SVG cached for 10 minutes.
curl -s -X POST "https://repo-stats-cards.skillsafe.ai/api/embed" \
-H "Authorization: Bearer YOUR_APP_TOKEN" -H "Content-Type: application/json" \
-d '{"kind":"repo","target":"pallets/flask","card":"repo","theme":"auto"}'
# -> {"id":"eyJ2Ijox...","url":"https://repo-stats-cards.skillsafe.ai/api/card/eyJ2Ijox....svg","markdown":"[](...)",...}
import requests
r = requests.post("https://repo-stats-cards.skillsafe.ai/api/embed",
headers={"Authorization": "Bearer " + token},
json={"kind": "user", "target": "torvalds", "card": "stats", "theme": "dark"})
print(r.json()["markdown"]) # paste into README.md
const res = await fetch("https://repo-stats-cards.skillsafe.ai/api/embed", {
method: "POST",
headers: { Authorization: "Bearer " + token, "Content-Type": "application/json" },
body: JSON.stringify({ kind: "repo", target: "expressjs/express", card: "commits" }),
});
const { url, markdown } = await res.json();
Errors
| Status | Meaning |
|---|---|
| 400 | bad_login or bad_repo: not a valid GitHub username or owner/name; bad_request or bad_json (/api/embed): an unknown kind, card, theme or option |
| 401 | No token, a guest token, or an expired token |
| 404 | not_found: no public account or repository with that name |
| 429 | rate_limited: GitHub's hourly limit for the server is used up; reset is when it refills (Unix time) |
| 502 | GitHub did not answer or answered with an error |
| 503 | not_configured (/api/embed): live cards are not switched on for this server |
Errors come back as {"error":{"code","message"}}. Data: GitHub REST API, public endpoints only. Not affiliated with GitHub.