Skip to content

Instrument catalog

The catalog is a read-only API over the instruments Mr Charles knows: a taxonomy of instrument types (from grand pianos to triangles, in several languages, with an openly licensed picture each), models by manufacturer with specifications, identifiers (GTIN/UPC/EAN, MPN, SKU), images and variants (sizes, finishes, each with its own barcode).

All paths are under https://api.mrcharles.app/api/v1/instruments (api-pre.mrcharles.app in the sandbox). Responses follow the common envelope: { "success": true, "data": …, "meta": … }.

CallerHeaderLimit
Anonymousnone120 requests per minute per IP
Shop / integrationX-API-Key: mck_…the key’s own limit (default 600 per minute)

Ask developers@mrcharles.app for a key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 includes Retry-After. An unknown or revoked key answers 401 INVALID_API_KEY / API_KEY_REVOKED.

The taxonomy is a tree: category → group → family → sub-type. Every node is a family with a kind:

kindMeaningExamples
groupNavigation only; holds no modelsCymbals, Drum kits and sets, Guitars
instrumentAn instrument typeAlto saxophone, Cajón, Grand piano
setSeveral pieces sold togetherDrum kit, Shell pack, Cymbal pack, Hardware pack
componentA piece of a set or of an instrumentSnare drum, Crash cymbal, Hi-hat stand, Mouthpiece, Pickup
accessoryUsed with an instrumentDrumsticks, Straps, Cases, Stands
consumableWears out and is replacedDrumheads, Strings, Reeds, Picks

Families relate to each other: component_of (a snare is a component of a drum kit), accessory_for (sticks for drums and cymbals), consumable_for (heads for snares). A relation declared on a group applies to everything beneath it, and the API resolves that for you: ask for crash-cymbal and you get the accessories declared for Cymbals.

Each family also carries an attribute schema — the specification keys shops should fill for its models (diameter, alloy, weight for cymbals; size, tip, material for sticks), inherited down the tree.

EndpointNotes
GET/taxonomyThe whole tree with kind, labels, imageUrl, modelCount and totalModelCount per node. Cache it; it changes rarely
GET/categoriesTen categories with labels (en, es, ca, fr)
GET/families?category=percussion&kind=component&parent=cymbalsFlat list; every filter optional. parent=none gives top-level families
GET/families/{slug}One family: ancestors, children, components, partOf, accessories, consumables, usedWith, attributes (?include= to pick)
GET/families/{slug}/attributes · /components · /accessories · /consumablesThe same, one list at a time
GET /families/crash-cymbal · abridged
{
"slug": "crash-cymbal",
"kind": "component",
"path": "cymbals/crash-cymbal",
"labels": { "en": "Crash cymbal", "es": "Plato crash", "ca": "Plat crash", "fr": "Cymbale crash" },
"ancestors": [{ "slug": "cymbals", "kind": "group" }],
"partOf": [{ "slug": "drum-kit", "relation": "component_of" }, { "slug": "cymbal-pack", "relation": "component_of" }],
"accessories": [{ "slug": "drumsticks", "via": "cymbals" }, { "slug": "drum-cases-and-bags", "via": "cymbals" }],
"consumables": [{ "slug": "cymbal-felts-and-sleeves", "via": "cymbals" }],
"attributes": [
{ "key": "diameter", "dataType": "number", "unit": "in", "isFilter": true, "inheritedFrom": "cymbals" },
{ "key": "alloy", "dataType": "enum", "options": ["B20", "B12", "B10", "B8", "brass", "nickel silver", "stainless steel", "other"], "isFilter": true, "inheritedFrom": "cymbals" },
{ "key": "weight", "dataType": "enum", "options": ["paper thin", "extra thin", "thin", "medium thin", "medium", "medium heavy", "heavy", "extra heavy"], "isFilter": true, "inheritedFrom": "cymbals" }
]
}

Show imageAttribution next to a family picture; the licence requires it for CC BY licences and it is good practice for public-domain ones.

EndpointNotes
GET/search?q=&category=&family=&manufacturer=&kind=&sort=&page=&perPage=Published models; full-text and fuzzy on name, manufacturer and aliases. family= includes everything under that family (cymbals finds crashes and rides); add shallow=true for the family alone. kind= filters by family kind; `sort=newest
GET/search?family=crash-cymbal&spec[diameter]=18&spec[alloy]=B20Attribute filters: spec[key]=value for any key in the family’s /attributes
GET/{idOrSlug}One model
GET/{idOrSlug}?include=allWith specifications, media, identifiers, variants, aliases in one call (include takes a comma list)
GET/{idOrSlug}/specifications · /media · /identifiers · /variants · /aliasesThe same, one resource at a time
GET/manufacturers?q= · /manufacturers/{idOrSlug}Brands

media only lists images whose rights were checked by a person and that carry a usable licence; url may be on our origin (/instruments/image?u=…) when the manufacturer’s CDN refuses cross-site loads.

EndpointNotes
GET/lookup?gtin=810054347026Also upc=, ean=, barcode= (any padded form is accepted), mpn=, sku=
GET /lookup?gtin=810054347026
{
"instrument": { "slug": "kala-west-coast-woods-tenor-xl-ukulele", "manufacturerName": "Kala", "familySlug": "ukulele", "…": "…" },
"identifier": { "identifierType": "upc", "value": "810054347026", "variantId": "a1f6…" }
}

identifier.variantId is set when the barcode belongs to one variant (a size or finish). 404 RESOURCE_NOT_FOUND when nothing carries the identifier.

EndpointNotes
GET/changes?since=2026-10-01T00:00:00Z&page=1&perPage=100Published and deprecated models changed at or after since, oldest first

Store the last updatedAt you processed and pass it back as since; overlapping by a second is fine (rows repeat, ids do not change). A model whose status is deprecated was withdrawn by the manufacturer: keep it for history, stop offering it.

Standard errors plus:

CodeMeaning
INVALID_API_KEYUnknown key
API_KEY_REVOKEDKey revoked by an administrator
RATE_LIMITEDOver the limit; see Retry-After
VALIDATION_ERRORMissing or malformed since, or no identifier given to /lookup

Manufacturers’ own catalogs (their stores’ public product data), reviewed before publication; instrument types from Wikidata and Wikimedia Commons. Prices are not part of the catalog.