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/toDate are 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-01 to 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 (15), 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.