StageRage Partner API
This guide explains how to call the venue-score API. It returns aggregate crew review scores for a venue. All requests must be signed with HMAC-SHA256 using the credentials issued to you (a Key ID and a Secret).
1. Credentials
You receive a Key ID (public, sent with each request) and a Secret (private, used to compute the signature — never sent over the wire). The secret is shown once at creation; store it securely. If lost, rotate the key to get a new one.
2. The endpoint
GET https://stagerage.online/api/v1/public/venue-score
?name=<venue name> (required)
&city=<city> (recommended)
&country=<country> (recommended)Each parameter may appear once. Without a city or country, the name must be at least 3 characters and lookups share a smaller global capacity, so a 429 can mean the service is busy rather than that your key is over its limit.
Provide city and country whenever possible — they disambiguate venues with similar names and greatly improve match accuracy. If no confident match is found, the API returns a 404 with a reason (rather than guessing).
3. How to sign a request
For every request, build a "string to sign", compute an HMAC-SHA256 signature of it using your secret, and send three headers. The string to sign is five lines joined by newline characters:
METHOD e.g. GET PATH e.g. /api/v1/public/venue-score (path only, NO query string) TIMESTAMP current unix time in milliseconds SHA256(query) hex SHA-256 of the canonical query string (see below); "" if there is none SHA256(body) hex SHA-256 of the request body; for GET the body is "" (empty)
So the string to sign is literally:
stringToSign = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + sha256_hex(canonicalQuery) + "\n" + sha256_hex(body)
Canonical query: take every name=value pair, percent-decode it, re-encode name and value with RFC 3986 rules (only A-Z a-z 0-9 - _ . ~ stay unescaped; spaces become %20; use uppercase hex), sort the pairs by name and then value, and join them with "&". For example name=O2 Academy&city=Brixton becomes city=Brixton&name=O2%20Academy. The query is therefore signed, so it cannot be changed after you sign.
Then compute the signature and send these headers:
X-SR-Key: <your Key ID> X-SR-Timestamp: <the same TIMESTAMP you signed> X-SR-Signature: hex( HMAC_SHA256(secret, stringToSign) )
Important details: the PATH is signed without the query string, and the query is signed separately in canonical form. The timestamp must be within 5 minutes of our server time. Each signature can be used once: sign every request, including every retry, with a fresh timestamp. The signature is lowercase hex.
4. Worked example (bash)
KEY_ID="srk_live_xxxxxxxx" SECRET="srs_live_xxxxxxxx" TS=$(date +%s000) # unix ms METHOD="GET" PATH_ONLY="/api/v1/public/venue-score" QUERY="city=Brixton&country=United%20Kingdom&name=O2%20Academy" # canonical: sorted, RFC 3986 encoded QUERY_HASH=$(printf '%s' "$QUERY" | openssl dgst -sha256 -hex | sed 's/^.* //') BODY_HASH=$(printf '' | openssl dgst -sha256 -hex | sed 's/^.* //') STR=$(printf '%s\n%s\n%s\n%s\n%s' "$METHOD" "$PATH_ONLY" "$TS" "$QUERY_HASH" "$BODY_HASH") SIG=$(printf '%s' "$STR" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //') curl -s "https://stagerage.online$PATH_ONLY?$QUERY" \ -H "X-SR-Key: $KEY_ID" \ -H "X-SR-Timestamp: $TS" \ -H "X-SR-Signature: $SIG"
5. Worked example (Node.js)
const crypto = require("crypto");
const KEY_ID = "srk_live_xxxxxxxx";
const SECRET = "srs_live_xxxxxxxx";
const enc = v => encodeURIComponent(v).replace(/[!'()*]/g, c => "%" + c.charCodeAt(0).toString(16).toUpperCase());
const sha256 = v => crypto.createHash("sha256").update(v).digest("hex");
// params: plain object. Returns { query, headers } - send exactly this query.
function signedRequest(method, path, params, body = "") {
const ts = Date.now().toString();
const query = Object.entries(params).map(([k, v]) => [enc(k), enc(v)])
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0))
.map(([k, v]) => k + "=" + v).join("&");
const stringToSign = [method, path, ts, sha256(query), sha256(body)].join("\n");
const sig = crypto.createHmac("sha256", SECRET).update(stringToSign).digest("hex");
return { query, headers: { "X-SR-Key": KEY_ID, "X-SR-Timestamp": ts, "X-SR-Signature": sig } };
}
const path = "/api/v1/public/venue-score";
const { query, headers } = signedRequest("GET", path, { name: "O2 Academy", city: "Brixton", country: "United Kingdom" });
fetch("https://stagerage.online" + path + "?" + query, { headers })
.then(r => r.json())
.then(console.log);6. Response
On a confident match (200):
{
"matched": true,
"confidence": 0.92,
"venue": { "name": "O2 Academy Brixton", "city": "Brixton", "country": "United Kingdom" },
"scores": {
"overall": 4.3, "staff": 4.5, "sound": 4.1, "lighting": 4.0,
"stage": 4.2, "loadin_area": 3.8, "loadin_access": 4.0,
"bus_power": 3.5, "dressing_room": 4.4
},
"_note": "Aggregate scores only. Scores are 0 where no reviews exist for that topic."
}Scores are on a 0–5 scale. A score of 0 means no reviews exist for that topic yet (not a rating of zero). When no confident match is found, you get a 404 with a reason such as "below_confidence_threshold" or "ambiguous_match", plus a best_guess to help you refine the query.
7. Errors & limits
401 = signature/auth failure (check your string-to-sign, timestamp freshness, and that PATH excludes the query string). 429 = rate limit exceeded — back off and retry after the Retry-After header. 400 = missing required "name". 404 = no confident venue match. Each key has a per-minute rate limit shown in the X-RateLimit-Limit response header.
8. Query parameter formats
Send parameters as URL-encoded query string values:
name (required) — the venue name. Matching is fuzzy and tolerant of punctuation, accents, and common filler words ("The", "Club", "Hall", etc.), so "O2 Academy" matches "The O2 Academy". Accuracy improves a lot when you also send city and country.
city (recommended) — the city name. Matched case-insensitively but otherwise exactly, so spelling should match the real city (e.g. "Ljubljana", "New York").
country (recommended) — the country. We normalize common variants automatically, so you can send "UK", "U.K.", "GB", "Great Britain", or "United Kingdom" and they all resolve to the same country; likewise "US", "USA", "United States". When in doubt, the full English country name is always accepted (e.g. "Slovenia", "Germany", "United States", "United Kingdom").
If you send a city/country that matches no venues, you get a 404 with reason "no_venues_in_scope" — that means the scope filter excluded everything before name matching even ran. Double-check the city/country spelling.
9. Troubleshooting
401 "Signature verification failed" — your computed signature didn't match ours. The usual causes, in order of likelihood: (a) the PATH you signed includes the query string — it must NOT; sign only "/api/v1/public/venue-score" and put the canonical query in its own hash line. This is also the error you get from clients still using the old four-line format that left the query out. (b) You signed a different timestamp than the one you sent in X-SR-Timestamp — they must be identical. (c) Your string-to-sign uses real newlines; make sure you join with the newline character, not the literal text backslash-n. (d) The body hash is wrong; for GET it must be the SHA-256 of an empty string. (e) The query hash does not match the canonical query (sorted, RFC 3986 encoded).
401 "Signature already used" — each signature is accepted once. Sign again with a fresh timestamp, including when retrying a failed request.
401 "Request timestamp outside the allowed window" — your clock is more than 5 minutes off from ours, or you reused an old timestamp. Generate a fresh timestamp per request and keep your server clock synced (NTP).
401 "Invalid or revoked API key" — the X-SR-Key is wrong, or the key was revoked/rotated. If you rotated, update both the key_id and the secret.
429 "Rate limit exceeded" — back off and retry after the Retry-After header (seconds). The X-RateLimit-Limit and X-RateLimit-Remaining headers tell you your budget.
404 "matched: false" — not an error. Either no venue in scope (check city/country), or no confident name match. The response includes a best_guess to help you refine.
10. Quick checklist
Before going live: (1) confirm a signed GET returns 200 for a venue you know exists; (2) confirm your timestamp is in milliseconds and fresh per request; (3) confirm you sign the path without the query string and the canonical query hash as the fourth line; (4) handle 404 (no match) and 429 (rate limit) gracefully; (5) store your secret securely and never send it in a request — it's only used to compute the signature.
Questions or a key request? Contact StageRage support.