LogoStacked
API

Campaigns

Fetch personalized offers and campaigns for players

Campaigns endpoints allow you to fetch personalized offers for players based on their profile, behavior, and the targeting conditions you've configured.

Endpoints Overview

EndpointAuthDescription
POST /client/player/campaignsJWTFetch campaigns from client
POST /player/campaigns/refreshAPI KeyFetch campaigns from server

Fetch Player Campaigns (Client)

Fetch all active offers and campaigns for a player from the client-side.

Endpoint

POST /client/player/campaigns

Authentication

JWT - Requires x-game-jwt header. The playerId and gameId are extracted from the JWT.

Request

{
  viewingCampaigns?: boolean | null; // Optional - Set to true when player is viewing offers UI
}
curl -X POST https://api.pixels.xyz/v1/client/player/campaigns \
  -H "Content-Type: application/json" \
  -H "x-game-jwt: YOUR_JWT_TOKEN" \
  -d '{"viewingCampaigns": true}'

Response

{
  player: IClientPlayer;        // Player object with snapshot and data
  offers: IClientOffer[];       // Array of personalized offers
  snapshot: IPlayerSnapshot;    // Player's current profile
  playerData: IPlayerData;      // Player's currency data
}
{
  "player": {
    "snapshot": {
      "_id": "snapshot-123",
      "gameId": "my-game",
      "playerId": "player-123",
      "username": "CoolPlayer",
      "daysInGame": 45,
      "loginStreak": 7,
      "trustScore": 850
    },
    "data": {
      "gameId": "my-game",
      "playerId": "player-123",
      "currencies": {
        "coins": {
          "id": "coins",
          "name": "Coins",
          "balance": 1500,
          "image": "https://cdn.example.com/coins.png"
        }
      }
    }
  },
  "offers": [
    {
      "offerId": "offer-abc123",
      "instanceId": "instance-def456",
      "playerId": "player-123",
      "gameId": "my-game",
      "name": "Weekend Special",
      "description": "Spend 100 coins to get 50 gems!",
      "image": "https://cdn.example.com/weekend-special.png",
      "status": "surfaced",
      "rewards": [
        {
          "kind": "item",
          "rewardId": "gems",
          "name": "Gems",
          "amount": 50,
          "image": "https://cdn.example.com/gems.png"
        }
      ],
      "completionConditions": {
        "spendCurrency": {
          "id": "coins",
          "name": "Coins",
          "amount": 100
        }
      },
      "completionTrackers": {
        "spendCurrency": 0
      },
      "createdAt": "2025-01-15T10:00:00Z",
      "expiresAt": "2025-01-17T10:00:00Z"
    }
  ],
  "snapshot": {
    "_id": "snapshot-123",
    "gameId": "my-game",
    "playerId": "player-123",
    "username": "CoolPlayer",
    "daysInGame": 45,
    "loginStreak": 7,
    "trustScore": 850,
    "currencies": {
      "coins": {
        "balance": 1500,
        "in": 2000,
        "out": 500
      }
    },
    "levels": {
      "character": {
        "level": 25
      }
    }
  },
  "playerData": {
    "gameId": "my-game",
    "playerId": "player-123",
    "currencies": {
      "coins": {
        "id": "coins",
        "name": "Coins",
        "balance": 1500,
        "image": "https://cdn.example.com/coins.png"
      }
    }
  }
}

Example Usage

async function loadPlayerOffers(jwt: string) {
  const response = await fetch('https://api.pixels.xyz/v1/client/player/campaigns', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-game-jwt': jwt
    },
    body: JSON.stringify({
      viewingCampaigns: true
    })
  });

  const { offers, snapshot, playerData } = await response.json();

  // Display offers to player
  offers.forEach(offer => {
    console.log(`${offer.name}: ${offer.description}`);
    console.log(`Status: ${offer.status}`);
    console.log(`Rewards:`, offer.rewards);

    if (offer.completionConditions) {
      console.log(`Completion:`, offer.completionConditions);
      console.log(`Progress:`, offer.completionTrackers);
    }
  });

  // Display player stats
  console.log(`Player Level:`, snapshot.levels);
  console.log(`Balance:`, playerData.currencies);
}

Fetch Player Campaigns (Server)

Fetch all active offers and campaigns for a player from your server.

Endpoint

POST /player/campaigns/refresh

Authentication

API Key - Requires x-api-key and x-client-id headers

Request

{
  playerId: string;              // Required - Player's unique ID
  viewingCampaigns?: boolean | null; // Optional - Set to true when player is viewing offers
}
curl -X POST https://api.pixels.xyz/v1/player/campaigns/refresh \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-client-id: YOUR_CLIENT_ID" \
  -d '{
    "playerId": "player-123",
    "viewingCampaigns": false
  }'

Response

{
  player: IClientPlayer;        // Player object with snapshot and data
  offers: IClientOffer[];       // Array of personalized offers
  token: string;                // One-time token for dashboard access
  gameId: string;               // Game ID
  playerSnapshot: IPlayerSnapshot; // Player's current profile
  playerData: IPlayerData;      // Player's currency data
}

The response includes an additional token field which can be used to redirect the player to the Stacked dashboard.

{
  "player": {
    "snapshot": {...},
    "data": {...}
  },
  "offers": [...],
  "token": "session-token-abc123...",
  "gameId": "my-game",
  "playerSnapshot": {...},
  "playerData": {...}
}

Example Usage

async function refreshPlayerCampaigns(playerId: string) {
  const response = await fetch('https://api.pixels.xyz/v1/player/campaigns/refresh', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': process.env.STACKED_API_KEY!,
      'x-client-id': process.env.STACKED_CLIENT_ID!
    },
    body: JSON.stringify({
      playerId,
      viewingCampaigns: false
    })
  });

  const { offers, playerSnapshot } = await response.json();

  // Check if player has any claimable offers
  const claimableOffers = offers.filter(o => o.status === 'claimable');

  if (claimableOffers.length > 0) {
    console.log(`Player ${playerId} has ${claimableOffers.length} claimable offers`);
  }

  return { offers, playerSnapshot };
}

Offer Statuses

Each offer returned has a status field indicating its current state:

StatusDescription
inQueueQueued but not yet surfaced (max slots reached)
surfacedActive and tracking progress
viewedPlayer has seen the offer
claimableReady to claim rewards
claimedRewards already claimed
expiredTime limit reached before completion

Status Flow

Offers typically flow: surfacedviewedclaimableclaimed. Setting viewingCampaigns: true automatically updates surfaced offers to viewed when fetched.