> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rigaly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mass codes for physical products

> Print tens of thousands of unique loyalty codes on your products — pay only when they're redeemed

Put unique, single-use loyalty codes on bottle caps, boxes, tags, or receipts. Customers type or scan the code in the Rigaly app and instantly earn points at your business.

**Key properties:**

* Up to **32,768 codes per batch**, generated instantly — create multiple batches for larger runs. (Why exactly 32,768? Each code's serial number occupies 3 characters of the 32-character code alphabet, and 32³ = 32,768 — keeping codes as short as possible.)
* Codes are cryptographically signed — they can't be guessed or counterfeited, and Rigaly doesn't need to store them individually. Counterfeiting attempts are counted per batch (`failed_attempts` in the stats) and you can deactivate a batch at any time.
* **You're only charged the per-transaction fee when a code is actually redeemed.** Printing a million caps costs you nothing in Rigaly fees until customers start claiming.
* Codes are human-friendly: `SUMMER-595Y-003-GGC-98R` — just 13 characters to type (the prefix and hyphens are optional), no ambiguous characters (no I/L/O/U), case-insensitive.

## 1. Create a batch

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.rigaly.com/api/v1/code-batches \
    -H "Authorization: Bearer rgly_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Summer 2027 bottles",
      "points": 250,
      "quantity": 30000,
      "expires_at": "2027-12-31T23:59:59Z",
      "prefix": "SUMMER"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.rigaly.com/api/v1/code-batches", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RIGALY_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Summer 2027 bottles",
      points: 250,          // each code awards 250 points
      quantity: 30000,
      expires_at: "2027-12-31T23:59:59Z",
      prefix: "SUMMER",     // cosmetic, appears on every printed code
    }),
  });
  const { batch, example_code } = (await response.json()).data;
  console.log(`Batch ${batch.id} — codes look like: ${example_code}`);
  ```

  ```python Python theme={null}
  response = requests.post(
      "https://api.rigaly.com/api/v1/code-batches",
      headers={"Authorization": f"Bearer {os.environ['RIGALY_API_KEY']}"},
      json={
          "name": "Summer 2027 bottles",
          "points": 250,
          "quantity": 30000,
          "expires_at": "2027-12-31T23:59:59Z",
          "prefix": "SUMMER",
      },
  )
  data = response.json()["data"]
  print(f"Batch {data['batch']['id']} — codes look like: {data['example_code']}")
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init("https://api.rigaly.com/api/v1/code-batches");
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          "Authorization: Bearer " . getenv("RIGALY_API_KEY"),
          "Content-Type: application/json",
      ],
      CURLOPT_POSTFIELDS => json_encode([
          "name" => "Summer 2027 bottles",
          "points" => 250,
          "quantity" => 30000,
          "expires_at" => "2027-12-31T23:59:59Z",
          "prefix" => "SUMMER",
      ]),
  ]);
  $data = json_decode(curl_exec($ch), true)["data"];
  echo "Codes look like: " . $data["example_code"];
  ```
</CodeGroup>

## 2. Export the codes for production

Download the full list as CSV (columns `code,serial,deep_link`) and hand it to your printer or packaging manufacturer. Exports stream and finish in seconds even for a full batch, and can be re-downloaded any time.

```bash theme={null}
curl "https://api.rigaly.com/api/v1/code-batches/{batchId}/export?format=csv" \
  -H "Authorization: Bearer rgly_sk_YOUR_KEY" \
  -o summer-2027-codes.csv
```

## 3. Print each code as a deep-link QR (required)

A bare code printed on a bottle cap is meaningless out of context — customers won't know what it's for or where to enter it. **Always print the QR code of the `deep_link` URL** the export gives you for every code:

```
HTTPS://LINK.RIGALY.COM/CODE/595Y003GGC98R
```

The `deep_link` is already QR-optimized: uppercase (QR's dense alphanumeric mode) and carrying only the 13 essential characters — no prefix, no dashes — so every batch's QR fits a **29×29 matrix with level-Q error correction** (25% of the print can be damaged and still scan), whether or not the batch uses a prefix. Print the formatted `code` column (`SUMMER-595Y-003-GGC-98R`) next to the QR — that's the human-facing version for support calls and manual entry.

Scanning it with any phone camera does the right thing automatically:

* **Rigaly app installed** → the app opens with the code pre-filled, one tap to claim.
* **App not installed** → an install page opens (App Store / Google Play); after installing, the customer can claim the printed code.
* **Scanned on a desktop** → a page shows the code and a QR to continue on the phone.

Print the human-readable code next to the QR as a fallback — the app also accepts typed codes (case-insensitive, hyphens optional).

### Generate the QR images

Any standard QR library works — feed the `deep_link` column straight in, one image per row:

<CodeGroup>
  ```javascript Node.js theme={null}
  // npm install qrcode csv-parse
  import QRCode from "qrcode";
  import { parse } from "csv-parse/sync";
  import fs from "node:fs";

  const rows = parse(fs.readFileSync("summer-2027-codes.csv"), { columns: true });
  for (const { code, deep_link } of rows) {
    // deep_link is already QR-optimized; Q-level error correction
    // survives 25% print damage
    await QRCode.toFile(`qr/${code}.png`, deep_link, {
      errorCorrectionLevel: "Q",
      width: 600,
      margin: 2,
    });
  }
  ```

  ```python Python theme={null}
  # pip install "qrcode[pil]"
  import csv, qrcode

  with open("summer-2027-codes.csv") as f:
      for row in csv.DictReader(f):
          # deep_link is already QR-optimized; Q-level error correction
          # survives 25% print damage
          qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_Q)
          qr.add_data(row["deep_link"])
          qr.make(fit=True)
          qr.make_image().save(f"qr/{row['code']}.png")
  ```
</CodeGroup>

Try it — scan this example with your phone camera (29×29, error correction Q). It encodes `HTTPS://LINK.RIGALY.COM/CODE/595Y003GGC98R`:

<Frame caption="Demo QR for https://link.rigaly.com/code/595Y003GGC98R — it opens the Rigaly app (or the install page), but the demo code itself isn't claimable.">
  <img src="https://mintcdn.com/rigaly/mFJ_TDdqAOK_AFzH/images/qr-demo-code.png?fit=max&auto=format&n=mFJ_TDdqAOK_AFzH&q=85&s=89c998ad19d8c2cfa03acef8d1c1da9a" alt="Example deep-link QR code" width="240" data-path="images/qr-demo-code.png" />
</Frame>

## 4. Customers claim in the Rigaly app

No integration needed on your side: customers scan the QR (or type the code) and the points land instantly — tier multipliers included. Each code works exactly once.

## 5. Monitor and manage

```bash theme={null}
# Redemption stats
curl https://api.rigaly.com/api/v1/code-batches/{batchId} \
  -H "Authorization: Bearer rgly_sk_YOUR_KEY"
# → { "claimed": 1204, "remaining": 28796, "points_issued": 301000, "failed_attempts": 0, ... }

# Customer support: check a specific code's status without claiming it
curl -X POST https://api.rigaly.com/api/v1/code-batches/verify \
  -H "Authorization: Bearer rgly_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "SUMMER-595Y-003-GGC-98R"}'

# Kill switch: stop all further claims (e.g. codes leaked before launch)
curl -X POST https://api.rigaly.com/api/v1/code-batches/{batchId}/deactivate \
  -H "Authorization: Bearer rgly_sk_YOUR_KEY"
```

## Billing

| Event                          | Per-transaction fee                                                                           |
| ------------------------------ | --------------------------------------------------------------------------------------------- |
| Creating a batch (any size)    | **\$0**                                                                                       |
| Exporting / re-exporting codes | **\$0**                                                                                       |
| A customer redeems a code      | 1 standard transaction fee. See details in the [billing page](https://www.rigaly.com/billing) |

Unredeemed codes never cost anything — ideal for mass-produced items where only a fraction of codes get claimed.
