Skip to content
version one
← Betas in the guide

Beta codes API

Give each tester in your beta their own code, then let your app check and redeem it. Version One hands out the codes and keeps track of which are used. Your app decides what a code unlocks.

How it fits together

  1. Open a beta on your live listing and tick Make a code for each tester as they join. Or paste your own codes, or send them with /add.
  2. A member joins. They get their own code, shown only to them on your listing: your next unused one if you added some, otherwise a new one Version One makes. With automatic codes on, they never run out.
  3. They bring it to your app: typed in, or already in the test link if you put {code} in it.
  4. Your server calls /redeem. Version One says if it's one of yours and marks it used, once.
Member joins your beta ──► Version One gives them V1-7K2QM-PXW4D
                                    │
           yourapp.co.za/?code=V1-7K2QM-PXW4D   (or they type it in)
                                    │
Your server ── POST /api/beta-codes/redeem ──► {"valid": true, "status": "redeemed"}
                                    │
                     Your app unlocks what you promised

Which betas take codes

How people get inCodes
Open link (a web app, an APK)Yes, and the link can carry the code
I'll add themYes, shown once you've marked them added
TestFlightNo. Use App Store Connect offer codes
Google Play testNo. Use Play Console promo codes

Apple and Google decide who gets into their tests, and their stores have their own codes, so those betas don't use these.

Your key

Make a key in Settings, under Beta codes key. It starts with v1b_, works for all your betas, and only shows once. Replacing it stops the old one at once.

Send it on every request as Authorization: Bearer v1b_…. Keep it on your server, as a secret (an environment variable or a secrets store). Never put it in a mobile app, a web page or a public repo: the API sends no CORS headers, so browsers can't call it anyway.

Each key can make 60 requests a minute. Past that you get a 429; wait a minute and try again.

Endpoints

All under https://versionone.co.za/api/beta-codes. Bodies are JSON. Codes are case-sensitive and matched exactly.

POST /redeem

Marks a code used, once. Safe to retry: a second call says already_redeemed.

curl -X POST https://versionone.co.za/api/beta-codes/redeem \
  -H "Authorization: Bearer $V1_BETA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "V1-7K2QM-PXW4D"}'

{"valid": true, "status": "redeemed", "beta": "Kasi Cuts", "project": "kasi-cuts"}

POST /check

Looks without changing anything. Same body and answer, with unused for a code that can still be redeemed.

statusvalidMeans
redeemedtrueThis call redeemed it. Unlock the thing.
already_redeemedtrueIt was redeemed before. Your app decides if that's fine, say the same player signing in again.
unusedtrueFrom /check only: given to a tester, not redeemed yet.
beta_closedfalseThe beta has closed, so its codes no longer redeem.
unknownfalseNot one of yours, or not given to anyone yet.

A code only counts once Version One has given it to a tester, so a list of unused codes can't be passed around. Answers never say who holds a code.

POST /add

You only need this if you'd rather use codes your own system made, or want a batch ready before people join. Adds codes to the open beta on one of your listings, by its slug (the last part of the listing's address). Send your own in codes, or ask Version One to make up to 500. New codes go first to testers who were waiting for one.

curl -X POST https://versionone.co.za/api/beta-codes/add \
  -H "Authorization: Bearer $V1_BETA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project": "kasi-cuts", "make": 20}'

{"added": 20, "repeats": 0, "made": ["V1-7K2QM-PXW4D", "..."],
 "total": 20, "given": 0, "redeemed": 0, "left": 20, "waiting": 0, "auto": false}

made lists the codes Version One made, so you can store them too. Your own codes are 3 to 64 characters: letters, numbers and symbols, with no spaces, commas or @. Repeats are skipped. A beta holds up to 1 000 codes.

GET /summary?project=slug

How many codes the open beta has, and how many are given, redeemed and left, and testers waiting for one. auto is true when Version One makes a code for each tester, so left doesn't matter.

curl "https://versionone.co.za/api/beta-codes/summary?project=kasi-cuts" \
  -H "Authorization: Bearer $V1_BETA_KEY"

{"beta": "Kasi Cuts", "project": "kasi-cuts", "total": 15, "given": 15, "redeemed": 8, "left": 0, "waiting": 0, "auto": true}

On an open-link beta, put {code} in your test link where the code goes, like https://yourapp.co.za/?code={code}. Each tester's button opens the link with their own code in it. Read it on your side, keep it until they sign in if you need to, then call /redeem from your server.

Example: a server function

// For example a Supabase Edge Function or a Cloudflare Worker.
// V1_BETA_KEY is a secret, never sent to the browser.
export async function redeemBetaCode(code: string) {
  const res = await fetch("https://versionone.co.za/api/beta-codes/redeem", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.V1_BETA_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ code }),
  });
  if (res.status === 429) throw new Error("Slow down and try again in a minute");
  if (!res.ok) throw new Error(`Version One said ${res.status}`);
  const answer = await res.json(); // { valid, status, beta?, project? }
  return answer.valid; // then unlock, and keep your own record
}

Errors

Errors come back as {"error": "...", "message": "..."}.

  • 401 missing_key or bad_key: no key, or one that was replaced or turned off.
  • 400 bad_body, bad_codes, bad_make, nothing_to_add: fix the request; bad_codes lists the codes it couldn't use.
  • 404 no_beta: none of your listings with that slug has an open beta that takes codes.
  • 413 too_big: send fewer codes at a time.
  • 429 slow_down: more than 60 requests in a minute.

Privacy

  • Which member holds which code stays between them and Version One. You and the API see whether a code is given or redeemed, not who has it.
  • Once your app redeems a code, you'll know which of your own accounts used it. That's yours to look after under POPIA, like the rest of your app's data.
  • Codes are deleted 30 days after the beta closes. Keep your own record of what you unlocked.
  • The code is your perk to give. Testers see it as from you, not from Version One. Privacy notice