Endpoints
The four segment manifests share one shape: count and apps differ, and so does updated — the
latest verification-or-addition date within that segment,
not catalogue-wide. Everything else is identical, so a client that
reads one manifest can read them all. /api/{slug}.json
serves one full entry at a time.
| Path | Returns | Entries |
|---|---|---|
/api/index.json | The whole catalogue manifest | 729 |
/api/apps.json | The app manifest | 371 |
/api/games.json | The game manifest | 355 |
/api/os.json | The operating-system manifest | 3 |
/api/{slug}.json | One entry, with every shortcut | 729 |
Payload schema
Every field actually emitted by /api/{slug}.json,
field by field. The lightweight manifest entries in the segment
manifests above carry a subset of these same values (slug, name, category, totalShortcuts, lastVerified, hash, page) plus one field of their own: data, the absolute URL of the full payload below for
that entry.
| Field | Type | Meaning |
|---|---|---|
slug | string | URL-safe identifier — also the {slug} in this endpoint's own path. |
name | string | Display name. |
category | string | Raw category id, e.g. "productivity", "gaming", "os". |
categoryLabel | string | Human-readable label for category. |
os | string[] | Desktop OS this shortcut data targets: any of "mac", "win", "linux". |
platforms | string[] | Release availability for games — any of "pc", "xbox", "playstation", "switch", "switch2". Ground truth, independent of what binding data this entry holds: a game listed as "xbox" may still carry keyboard-only shortcuts. Empty for non-games. |
website | string | null | The app's own site, when known. |
stores | object | null | Official storefront URLs present for this entry (appStore, macAppStore, steam, gog, epic) — only existing keys are included. |
description | string | One-line summary. |
totalShortcuts | number | Count of shortcut/command entries across all groups. |
added | string | ISO date the entry joined the catalogue. |
lastVerified | string | null | ISO date a human last checked the data against sources; null if never verified. |
sources | string[] | Official-doc URLs the data was checked against. May be empty. |
hasPoster | boolean | True when at least one live buy channel exists for this entry. |
hash | string | Content-only fingerprint (12 hex chars) — see dataVersion & hash below. |
page | string | Absolute URL of this entry's human-readable page. |
groups[] | array | The shortcut list, grouped by category. |
groups[].category | string | Group heading, e.g. "Editing", "Navigation". |
groups[].items[] | array | Shortcuts or commands in this group. |
groups[].items[].name | string | The action's label. |
groups[].items[].mac | string, optional | Present only when a Mac binding exists. |
groups[].items[].win | string, optional | Present only when a Windows binding exists. |
groups[].items[].linux | string, optional | Present only when a Linux binding exists. |
groups[].items[].gamepad | string, optional | Canonical gamepad binding (games only). |
groups[].items[].type | "command", optional | Present only for typed-command entries; absent means a key-chord shortcut. |
dataVersion and hash
Every manifest carries a top-level dataVersion, rolled
up from every app's own hash. Each app's hash fingerprints one subset of that entry — slug, name, category, os, platforms, lastVerified,
and every shortcut and binding in the entry — but not the rest of
the payload: categoryLabel, website, stores, description, totalShortcuts, added, sources, hasPoster, page, and
the order of the items within each group — which follows an
editorial priority the payload does not expose — all sit outside
it. Editing one of those alone changes the JSON a
client receives but moves neither the per-app hash
nor dataVersion. So comparing dataVersion,
then per-app hash, reliably catches changed shortcuts,
bindings, or a new verification date — it is not a full-payload
integrity check.
{
"dataVersion": "e3f1a9c7d5b2846f",
"apps": [
{
"slug": "blender",
"hash": "9f3a2c1d4b6e",
"data": "https://shortcutposters.com/api/blender.json"
}
]
} Feeds
Recently-added entries, as RSS and JSON Feed, one pair per segment.
| Segment | Format | Feed |
|---|---|---|
| all | XML | ShortcutPosters — recently added |
| all | JSON | ShortcutPosters — recently added |
| apps | XML | ShortcutPosters — recently added apps |
| apps | JSON | ShortcutPosters — recently added apps |
| games | XML | ShortcutPosters — recently added games |
| games | JSON | ShortcutPosters — recently added games |
| os | XML | ShortcutPosters — recently added operating systems |
| os | JSON | ShortcutPosters — recently added operating systems |
Version policy
- Existing field names never disappear and never change type.
- Adding a new field is not a breaking change.
dataVersionchanges exactly when a fingerprinted field changes — see dataVersion and hash above for what that covers.-
A breaking change would live under a new
/api/v2/path — the current, flat paths stay in place during the transition./api/v1/already works today as an alias of those same flat paths.
Terms of use
- Keyboard shortcuts and gamepad bindings are facts: nobody owns them, and you are free to use them.
- The compilation is our editorial work — the normalisation, the categories, and the source and verification metadata attached to every entry.
- Reuse of this data requires visible attribution: credit ShortcutPosters and link to shortcutposters.com.
- Reselling or redistributing the compiled database, in whole or in substantial part, is not permitted.
- These terms cover the data only. Logos, icons and cover art shown on the site are their owners' and are not licensed here.
Examples
The games manifest, then one entry's shortcut groups:
# The games manifest, then the detail of one entry
curl -s https://shortcutposters.com/api/games.json | jq '.apps[0]'
curl -s https://shortcutposters.com/api/baldurs-gate-3.json | jq '.groups[0]' The same walk in JavaScript, following the manifest's own links:
const index = await fetch("https://shortcutposters.com/api/apps.json").then((r) => r.json());
const app = index.apps.find((a) => a.slug === "blender");
const data = await fetch(app.data).then((r) => r.json());
console.log(data.groups.flatMap((g) => g.items).length, "shortcuts");