Advanced

Power-user corner: things you can build against PianoKitty. Not needed for normal practice.

Rewards API — serve your own reward images

In game mode the app can show a small reward image after correct chords. Besides the built-in cat photos you can plug in your own HTTP endpoint — any URL that returns JSON pointing at an image. Your kid's drawings, family photos, memes — whatever motivates you.

1. The contract

The app sends a plain GET request to your URL and expects this JSON back:

{ "success": 1, "src": "https://your.host/rewards/cat-042.jpg" }
  • success must be the number 1 — the string "1" is rejected.
  • src — absolute URL of the image to display (jpg/png/gif/webp…).
  • Return a different src per call — randomization is your endpoint's job.

Anything else — non-200 status, malformed JSON, success ≠ 1, empty src, or a response slower than 2 seconds — is silently dropped: practice continues, just without the image. No error is shown.

2. Requirements

  • HTTPS. The app runs on HTTPS, so the browser refuses plain-HTTP endpoints (mixed content).
  • CORS. The request comes from your browser, cross-origin. Your endpoint must send Access-Control-Allow-Origin: * (or the app's origin).
  • The image URL itself needs no CORS headers — it is loaded with a regular <img> tag.

3. Minimal implementations

PHP — drop a folder of images next to this script:

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$images = glob(__DIR__ . '/rewards/*.{jpg,png,gif,webp}', GLOB_BRACE);
echo json_encode([
    'success' => 1,
    'src' => 'https://your.host/rewards/' . basename($images[array_rand($images)])
]);

Cloudflare Worker — no server needed, free tier is plenty:

const IMAGES = [
  'https://your.host/img/1.jpg',
  'https://your.host/img/2.jpg',
  'https://your.host/img/3.jpg'
];
export default {
  fetch() {
    const src = IMAGES[Math.floor(Math.random() * IMAGES.length)];
    return new Response(JSON.stringify({ success: 1, src }), {
      headers: {
        'content-type': 'application/json',
        'access-control-allow-origin': '*'
      }
    });
  }
};

Sanity-check from a terminal before wiring it into the app:

$ curl -s https://your.host/reward
{"success":1,"src":"https://your.host/rewards/cat-042.jpg"}

4. Use it in the app

  1. Open Preferences → Rewards.
  2. Set Reward images to Custom API and paste your endpoint URL.
  3. Rewards appear in game mode after correct chords. To get an image only every Nth correct chord, enable the milestone option and set the interval — the endpoint is then called only on milestones.

Troubleshooting

  • No image, no error — that is the designed failure mode. Open DevTools → Network and watch the request to your endpoint after a correct chord.
  • CORS error in the console → the Access-Control-Allow-Origin header is missing.
  • Works in curl but not in the app → check HTTPS, CORS, and that success is a number, not a string.
  • Images sometimes skipped → your endpoint took longer than the 2-second timeout.