Base URL and headers

https://<notonq-host>/q (or /q/app/<your-app> to name your app without a header) plus the same path and parameters you would send to https://hackathon.api.qloo.com. The machine-readable spec is at /openapi.json.

HeaderRequiredUse
X-Api-KeyyesYour Qloo key. Forwarded to Qloo per request, never stored.
X-NotOnQ-AppnoA label for your app (up to 64 characters), shown in the Ledger and on Proof. Same as /q/app/<label>.
X-Lastfm-KeynoYour own Last.fm key. Without it NotOnQ uses its own for the hackathon demo.

Endpoints

Qloo endpointWhat NotOnQ does
GET /search (types=urn:entity:artist, or no types)Qloo first, unchanged. When Qloo has no artist (untyped: nothing at all) with that exact name, NotOnQ adds the filled artist, or every artist the name could be, after Qloo's results.
GET /entities?entity_ids=…Qloo's IDs from Qloo; NotOnQ's (fills, and candidates it offered, filled on first use) from NotOnQ, in one results list.
GET /v2/insightsAny filter.type. NotOnQ IDs, and Qloo IDs Qloo rejects as a signal, become their stand-ins; Qloo answers. No stand-ins: an empty answer.
POST /v2/insightsThe same, with a JSON body; { id, weight } objects stay weighted (scaled by each stand-in's match).
GET /v2/analysis/compareNotOnQ artists in either group become their stand-ins.
Everything elsePasses through unchanged (cached per key): /, /v2/tags, /v2/audiences, /v2/trending, other entity types.

GET /search

Request
curl -H "X-Api-Key: $QLOO_API_KEY" \  "https://<notonq-host>/q/search?types=urn:entity:artist&query=sombr"
Response for an artist Qloo doesn't know
{  "results": [{ "entity_id": "502cf908-…", "name": "sombr", "types": ["urn:entity:artist"], … }],  "notonq": {    "served_by": "fallback",    "gap": { "kind": "G1", "qloo_status": 200 },    "cache": { "hit": false, "first_seen": "2026-10-23T06:51:45Z" },    "demand": { "requests": 1, "callers": 1, "apps": 1 },    "proxy": { "replaced": [], "stand_ins": [{ "entity_id": "EB24591A-…", "name": "The Neighbourhood", "weight": 0.89, "source": "lastfm" }] },    "provenance": { "name": [{ "source": "musicbrainz", "record": "https://musicbrainz.org/artist/…", "licence": "CC0-1.0", "method": "copied" }] },    "popularity": { "value": 0.982, "n": 4, "rmse": 0.029 },    "unavailable": [],    "confidence": "high",    "research": { "status": "none" },    "attribution": ["…"]  }}
When the name is ambiguous
# No clear match: Qloo's results first, then every artist the name could be, each a Qloo-shaped entry"results": [  { "entity_id": "94E61B56-…", "name": "Victoria Monét", … },                       # Qloo's, unchanged  { "entity_id": "e1e3771f-bb8e-5289-…", "name": "Victoria Queen", "types": ["urn:entity:artist"],    "properties": { "short_description": "Not in Qloo: a possible match NotOnQ found on lastfm, deezer (4 Last.fm listeners, 2 Deezer fans). …" } }],"notonq": { "served_by": "needs_choice", "candidates": [  { "key": "lf:victoria-queen", "name": "Victoria Queen", "exact": true, "listeners": 4, "fans": 2, "deezerId": 12775769, "links": [ … ] },  { "key": "mb:7db5c493-…", "name": "Queen Victoria", "exact": false, "country": "US", "inQloo": null, … }], "next": "Repeat the request with notonq.candidate=<key>" }
# Then use the one you mean: its entity_id works in /entities and as a signal (NotOnQ fills it on first use)curl -H "X-Api-Key: $QLOO_API_KEY" "https://<notonq-host>/q/entities?entity_ids=e1e3771f-bb8e-5289-…"# or by keycurl -H "X-Api-Key: $QLOO_API_KEY" \  "https://<notonq-host>/q/search?types=urn:entity:artist&query=victoria%20queen&notonq.candidate=lf:victoria-queen"
ParameterUse
notonq.candidate=<key>Fill from this candidate (keys come from notonq.candidates). Sending the candidate's entity_id to /entities does the same.
notonq.choose=1Always list the candidates, even when a fill is cached or one match is confident.

notonq.* parameters are never sent to Qloo.

GET and POST /v2/insights

Stand-ins answer for artists Qloo can't use
# GET: a NotOnQ ID works as a signal anywhere Qloo takes onecurl -H "X-Api-Key: $QLOO_API_KEY" \  "https://<notonq-host>/q/v2/insights?filter.type=urn:entity:artist&signal.interests.entities=502cf908-…&take=5"
# POST: Qloo's JSON body, with UUID strings or { id, weight } objectscurl -X POST -H "X-Api-Key: $QLOO_API_KEY" -H "Content-Type: application/json" \  "https://<notonq-host>/q/v2/insights" \  -d '{ "filter.type": "urn:entity:place", "filter.location.query": "Melbourne",        "signal.interests.entities": [{ "id": "502cf908-…", "weight": 1 }] }'

The response is Qloo's, plus notonq.proxy listing what was replaced and by which stand-ins.

The notonq block

FieldMeaning
served_byqloo · fallback · mixed (Qloo answered for stand-ins) · needs_choice
gapkind G1 or G2, with Qloo's status and message
cachehit, and when the fill was first made
demandrequests, distinct callers and apps for this artist
proxyreplaced IDs and the stand_ins used (entity_id, name, weight, source)
provenanceper field: source, record, licence, method, retrievedAt, note
popularitythe derived estimate: value, n stand-ins, rmse
unavailablefields with no provable source; '(source error: …)' when a source failed
confidencehigh (MusicBrainz record) or low (Last.fm or Deezer only)
candidates / nextfor needs_choice: the options and how to pick one
researchpending · done · failed · skipped · none

Status codes

CodeWhen
200Qloo answered, or NotOnQ filled the gap, or returned candidates
400Bad request body; or Qloo's own 400, passed through
401No X-Api-Key
404Qloo's 404 when nothing could be filled
429Your daily Qloo budget through NotOnQ is used up (resets 00:00 UTC), or Qloo's own rate limit
502Qloo did not answer