Orishare
Recipes

Weekly league with item rewards

A three-tier weekly league scored by your backend, paying gems and items for top ranks and promotion, with promotion messages.

The result: every week active players are grouped in cohorts of 30, ranked by the score your backend sends, and the top of each cohort moves up from Bronze to Silver to Gold. The top three of each cohort get gems, the winner of a Silver cohort also gets an item, and every promotion out of Silver grants a streak freeze item. See Leagues for every field.

You need: a secret key on your backend (programs:write) and a gems currency. A reward naming an item that does not exist yet waits as pending and is granted once the item exists, and the league refuses to activate until every item it names exists, so create the items first.

1. Create the two items

Items are configured through change sets (POST /v1/change_sets with an Idempotency-Key), one command per change set, each applied with POST /v1/change_set_applications like the league below (see Economy for every item field):

the frame
{
  "commands": [
    {
      "type": "economy_item.create",
      "key": "golden_frame",
      "item_type": "durable",
      "max_quantity": 1,
      "display": {
        "title": "Golden frame",
        "description": "Shown around the league winner's name."
      }
    }
  ]
}
the streak freeze
{
  "commands": [
    {
      "type": "economy_item.create",
      "key": "streak_freeze",
      "item_type": "consumable",
      "max_quantity": 5,
      "display": {
        "title": "Streak freeze",
        "description": "Keeps a streak through one missed day."
      }
    }
  ]
}

2. Propose the league

In a test environment, or in a live environment with auto autonomy, you can call POST /v1/leagues directly with the same body. Otherwise propose it (POST /v1/change_sets with an Idempotency-Key):

{
  "commands": [
    {
      "type": "league.create",
      "key": "weekly",
      "name": "Weekly league",
      "config": {
        "schema": "league/v1",
        "tiers": [
          {
            "key": "bronze",
            "name": "Bronze",
            "promote_count": 10,
            "rewards": [{ "rank_from": 1, "rank_to": 3, "currency": "gems", "amount": 30 }]
          },
          {
            "key": "silver",
            "name": "Silver",
            "promote_count": 7,
            "demote_count": 5,
            "rewards": [
              {
                "rank_from": 1,
                "rank_to": 1,
                "reward": [
                  { "currency": "gems", "amount": 80 },
                  { "type": "item", "item": "golden_frame", "quantity": 1 }
                ]
              },
              { "rank_from": 2, "rank_to": 3, "currency": "gems", "amount": 50 }
            ],
            "promotion_reward": { "type": "item", "item": "streak_freeze", "quantity": 1 }
          },
          {
            "key": "gold",
            "name": "Gold",
            "demote_count": 5,
            "rewards": [{ "rank_from": 1, "rank_to": 3, "currency": "gems", "amount": 100 }]
          }
        ],
        "cohort_size": 30,
        "period": {
          "kind": "week",
          "start_weekday": 1,
          "start_minute": 0,
          "timezone": "environment"
        },
        "score": { "source": "direct" },
        "settle_delay_minutes": 10,
        "min_score_to_promote": 1
      }
    }
  ]
}

Apply it with POST /v1/change_set_applications when your autonomy policy allows, or hand the change set id to a person to confirm. Then read the league (GET /v1/leagues, find key weekly) and propose { "type": "league.activate", "key": "weekly", "expected_version": <revision> } the same way. Activation checks that gems exists.

3. Score from your backend

Send a contribution whenever a player does what the league counts. Use your own business id as dedupe_key so a retry never counts twice:

const API = 'https://api.orishare.com';

async function addLeagueScore(leagueId, customerId, lessonId, xp) {
  const res = await fetch(`${API}/v1/leagues/${leagueId}/contributions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ORISHARE_SECRET_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `league-${lessonId}`, // one per intent, reused on retry
    },
    body: JSON.stringify({ customer_id: customerId, amount: xp, dedupe_key: `lesson:${lessonId}` }),
  });
  if (!res.ok) throw new Error(`league contribution failed: ${res.status}`);
  return res.json(); // { entry: { score, cohort_id, tier_index, ... }, replayed }
}

The first contribution of the week puts the player in a cohort of their tier.

4. Show it

From the app, with the publishable key and the player's customer token:

  • GET /v1/customer/leagues: tier, seconds_left, the player's rank, score and zone, and last week's result (outcome, reward, reward_status).
  • GET /v1/customer/leagues/{id}/standings: the cohort by display name, each with zone.

Or embed the hosted widget with the league surface.

5. Tell players

Create message types for the league events (POST /v1/notification_message_types, or the change set command notification_message_type.create):

{ "key": "league_promoted", "routes": [{ "channel": "mobile_push", "path": "builtin" }] }

Do the same for league_demoted and league_period_started. The catalogue copy is used until you add templates; see Notifications.

6. Test in the test environment

Contribute scores for a few test customers, then wait for the period to end plus settle_delay_minutes (a day period makes this quicker while testing). Check GET /v1/leagues/{id}/periods (status settled), the cohort with GET /v1/league_cohorts/{id}/standings (outcome per row), the players' balances and the messages in GET /v1/notification_messages?type_key=league_promoted (test environment messages are sent by the fake provider and never leave Orishare).

On this page