Feedback & Suggestions API
The Puzzle Studio exposes a read-only HTTP API for retrieving the feedback and suggestions that players submit. It is designed so you can page through and download 100% of your organisation’s records, and filter them by date.
Authentication
Create an API key in the studio under Settings → API keys and pick the permissions it needs
(feedback:read and/or suggestions:read). Send the key as a bearer token:
Authorization: Bearer YOUR_API_KEY
- A missing or invalid key returns 401.
- A key that lacks the required permission for the endpoint returns 403.
Keep keys secret — treat them like passwords. If a key leaks, revoke it in the studio.
Base URL
https://api.42puzzles.com
Replace YOUR_ORG_ID in the paths below with your organisation id (the one in the studio URL).
Endpoints
| Method | Path | Permission |
|---|---|---|
| GET | /api/YOUR_ORG_ID/feedback |
feedback:read |
| GET | /api/YOUR_ORG_ID/suggestions |
suggestions:read |
Both endpoints behave identically and accept the same query parameters. Records are returned newest first (by submission time).
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer | 100 |
Page size, 1–500. Values above 500 are capped at 500. |
cursor |
string | — | Opaque cursor from a previous response’s meta.nextCursor. See below. |
fromDate |
ISO 8601 date(time) | — | Only records with ts on or after this instant (inclusive). |
toDate |
ISO 8601 date(time) | — | Only records with ts on or before this instant (inclusive). |
Invalid fromDate/toDate values or a malformed cursor return 400.
Tip:
fromDate/toDateare compared against the exact instant you pass. To include a whole day as an upper bound, pass the start of the next day, e.g.toDate=2026-07-01to include all of 2026‑06‑30.
Response shape
Every response is a JSON envelope with a data array and a meta object. New metadata fields may
be added over time, so ignore fields you don’t recognise.
{
"data": [
{
"feedback": "Loved today's puzzle!",
"email": "",
"puzzleDate": "2026-07-09",
"playerId": "YOUR_ORG-abc123",
"puzzle": "Wordle",
"userId": "client-xyz",
"domain": "www.example.com",
"screenWidth": 390,
"screenHeight": 844,
"device": "web",
"rating": 5,
"userAgent": "Mozilla/5.0 ...",
"ts": "2026-07-09T12:34:56.000Z"
}
],
"meta": {
"count": 100,
"limit": 100,
"hasMore": true,
"nextCursor": "eyJzIjoxNzUyMDYz...=="
}
}
Feedback fields
| Field | Type | Description |
|---|---|---|
feedback |
string | Free-text feedback from the player. |
email |
string | Optional contact email. |
puzzleDate |
string | null | Publish date of the puzzle (YYYY-MM-DD), derived from the stored puzzle id. |
playerId |
string | Internal player id (includes org prefix). |
puzzle |
string | Display name of the player/game slot. |
userId |
string | Anonymous client id for the browser/app session. |
domain |
string | Hostname the game was played on. |
screenWidth |
number | Viewport width in pixels. |
screenHeight |
number | Viewport height in pixels. |
device |
"web" | "app" |
"app" when played inside a native webview; otherwise "web". |
rating |
number | null | Star rating (1–5), or null when no rating was given. |
userAgent |
string | Browser or app user-agent string. |
ts |
string | Submission timestamp (ISO 8601). |
Suggestions fields
Same as feedback, except:
| Field | Type | Description |
|---|---|---|
puzzleType |
string | Puzzle type (e.g. wordle, crossword). |
suggestion |
string | The suggested word or phrase. |
There is no feedback field on suggestions responses.
meta field |
Meaning |
|---|---|
count |
Number of records in this page’s data array. |
limit |
The effective page size that was applied. |
hasMore |
true when more records are available via nextCursor. |
nextCursor |
Pass this as cursor to fetch the next page. null when there are no more. |
Fetching a single page
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.42puzzles.com/api/YOUR_ORG_ID/feedback?limit=100"
Downloading 100% of the records (pagination)
The dataset can exceed the 500-per-page cap. To download everything, keep following
meta.nextCursor until it is null. Because the cursor encodes an exact position, you never miss
or duplicate a record even while new feedback keeps coming in.
Resend the same fromDate/toDate on every request — the cursor only encodes position, not
your filters.
Example: bash
#!/usr/bin/env bash
set -euo pipefail
ORG="YOUR_ORG_ID"
KEY="YOUR_API_KEY"
BASE="https://api.42puzzles.com/api/${ORG}/feedback?limit=500"
cursor=""
while :; do
url="$BASE"
[ -n "$cursor" ] && url="${BASE}&cursor=${cursor}"
resp=$(curl -sS -H "Authorization: Bearer ${KEY}" "$url")
# append this page's records to all.jsonl
echo "$resp" | jq -c '.data[]' >> all.jsonl
cursor=$(echo "$resp" | jq -r '.meta.nextCursor // empty')
[ -z "$cursor" ] && break
done
echo "Done. $(wc -l < all.jsonl) records written to all.jsonl"
Example: Python
import requests
ORG = "YOUR_ORG_ID"
KEY = "YOUR_API_KEY"
URL = f"https://api.42puzzles.com/api/{ORG}/feedback"
HEADERS = {"Authorization": f"Bearer {KEY}"}
records, cursor = [], None
while True:
params = {"limit": 500}
if cursor:
params["cursor"] = cursor
body = requests.get(URL, headers=HEADERS, params=params, timeout=30).json()
records.extend(body["data"])
cursor = body["meta"].get("nextCursor")
if not cursor:
break
print(f"Downloaded {len(records)} records")
Filtering by date
Combine date filters with pagination to export a specific window, e.g. everything from June 2026:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.42puzzles.com/api/YOUR_ORG_ID/suggestions?fromDate=2026-06-01&toDate=2026-07-01&limit=500"
Follow meta.nextCursor exactly as above (resending fromDate/toDate) to page through the whole
range.