Web / JavaScript Integration

The simplest integration. Use fetch() to call the Scrya API directly from your browser game.

Setup

// scrya.js — minimal client
const SCRYA_API = 'https://api.scrya.com/api/ads';
const API_KEY = 'sk_live_your_key_here';

async function scryaPost(endpoint, body) {
  const res = await fetch(SCRYA_API + endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + API_KEY,
    },
    body: JSON.stringify(body),
  });

  if (res.status === 204) return null; // No ad matched
  if (!res.ok) throw new Error('Scrya API error: ' + res.status);
  return res.json();
}

async function scryaGet(endpoint) {
  const res = await fetch(SCRYA_API + endpoint, {
    headers: { 'Authorization': 'Bearer ' + API_KEY },
  });
  if (!res.ok) throw new Error('Scrya API error: ' + res.status);
  return res.json();
}

Step 1: Match an Ad

async function matchAd(gameId, playerId, narrative, moodTags) {
  return scryaPost('/match', {
    game_id: gameId,
    user_id: playerId,
    player_geo: { country_code: 'US' },
    game_context: {
      theme_id: '3',
      category: 'simulation',
      narrative: narrative,
      mood_tags: moodTags,
    },
    session_id: crypto.randomUUID(),
  });
}

Step 2: Display the Hook

async function generateScene(gameId, playerId) {
  const narrative = 'The president faces a difficult decision...';
  const match = await matchAd(gameId, playerId, narrative, ['tense']);

  if (match) {
    // Inject hook into dialogue
    dialogueBox.addLine(match.narrative_hook);

    // Apply visual bias if your renderer supports it
    if (match.biased_pipeline) {
      renderer.setLighting(match.biased_pipeline.lighting);
      renderer.setEffects(match.biased_pipeline.effects);
    }

    // Record the impression
    await recordImpression(match, gameId);
  }
}

Step 3: Record the Impression

async function recordImpression(match, gameId) {
  if (!match) return;

  await scryaPost('/impressions', {
    campaign_id: match.campaign_id,
    creative_id: match.creative_id,
    game_id: gameId,
  });
}

TypeScript Types

interface AdMatchRequest {
  game_id: string;
  user_id: string;
  player_geo: { country_code: string; state_code?: string; city_name?: string };
  game_context: {
    theme_id?: string;
    category?: string;
    narrative?: string;
    mood_tags?: string[];
  };
  session_id?: string;
}

interface AdMatchResponse {
  campaign_id: string;
  creative_id: string;
  narrative_hook: string;
  creative_type: 'narrative_hook' | 'product_mention' | 'location_brand' | 'character_reference';
  brand_name: string;
  cpm_bid_cents: number;
  biased_pipeline?: {
    lighting?: string;
    effects?: string;
    location?: string;
    camera?: string;
  };
}

Check Your Revenue

// Check total earnings
const revenue = await scryaGet('/creator-revenue');
console.log('Total earnings:', revenue);

// Per-game breakdown
const gameRevenue = await scryaGet('/creator-revenue/' + gameId);
console.log('Game earnings:', gameRevenue);

React Example

function useScryaAd(gameId, playerId, narrative, moods) {
  const [match, setMatch] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    matchAd(gameId, playerId, narrative, moods)
      .then(setMatch)
      .catch(() => setMatch(null))
      .finally(() => setLoading(false));
  }, [gameId, playerId, narrative]);

  useEffect(() => {
    if (match) recordImpression(match, gameId);
  }, [match]);

  return { match, loading };
}

Next Steps