BricqsBricqs
Documentation

Headless SDK

Enterprise-grade React hooks that return pure data, state, and action handlers, zero rendering. Build your own UI with your design system while Bricqs handles all backend logic.

Architecture

The Headless SDK is a layer of React hooks that sit on top of the existing SDK infrastructure. Hooks call the Bricqs API through the SDK client, manage state with React, and return typed objects, you render everything yourself.

┌──────────────────────────────────────────────────────┐
│  Your Custom UI (your components, your design system) │
│                                                       │
│  useQuiz()    useSpinWheel()    useForm()             │
│  usePoll()    useSurvey()       useBadgesHeadless()   │
│  usePoints()  useTier()         useChallenge()        │
│  useLeaderboard()               useRewardsHeadless()  │
│  useReferral()  useContest()    useGates()            │
│  useActivityProgress()          useActivityCalendar() │
│                                                       │
│  Returns: data + state + handlers. Zero JSX.          │
├───────────────────────────────────────────────────────┤
│  BricqsProvider (context + SDK client)                │
│                                                       │
│  BricqsClient.activities, validate, complete         │
│  BricqsClient.challenges, enroll, progress           │
│  BricqsClient.leaderboards, rankings                 │
│  BricqsClient.rewards, claimed rewards               │
│  BricqsClient.badges, earned/unearned status         │
│  BricqsClient.points, balance, transactions          │
├───────────────────────────────────────────────────────┤
│  Bricqs API Server                                    │
│                                                       │
│  Activity validation + action execution               │
│  Points, badges, tiers, rewards (atomic, server-side) │
│  Challenge evaluation + progress tracking             │
└───────────────────────────────────────────────────────┘

Quick Start

Build a completely custom quiz UI in 5 minutes. Bricqs handles scoring, point awards, badge unlocks, and reward claims, you handle the rendering.

import {
  BricqsProvider,
  useEngagementData,
  useQuiz,
  usePoints,
} from '@bricqs/sdk-react';

// 1. Wrap your app
function App() {
  return (
    <BricqsProvider config={{ apiKey: 'bq_live_YOUR_KEY' }}>
      <MyCustomQuiz />
    </BricqsProvider>
  );
}

// 2. Load engagement and get quiz config
function MyCustomQuiz() {
  const { getQuizConfig, getComponent, isLoading } = useEngagementData({
    engagementId: 'YOUR_ENGAGEMENT_UUID',
  });

  const quizConfig = getQuizConfig();
  const quizComponent = getComponent('quiz');
  const points = usePoints({ engagementId: 'YOUR_ENGAGEMENT_UUID' });

  if (isLoading || !quizConfig || !quizComponent) {
    return <div>Loading...</div>;
  }

  return (
    <QuizUI
      engagementId="YOUR_ENGAGEMENT_UUID"
      activityId={quizComponent.id}
      config={quizConfig}
      pointsBalance={points.balance}
    />
  );
}

// 3. Build your own quiz UI, zero Bricqs styling
function QuizUI({ engagementId, activityId, config, pointsBalance }) {
  const quiz = useQuiz({ engagementId, activityId, config });

  if (quiz.isComplete) {
    return (
      <div className="my-results">
        <h2>{quiz.feedback?.title}</h2>
        <p>Score: {quiz.score?.percentage}%</p>
        {quiz.actionResults?.points_awarded > 0 && (
          <p className="points">+{quiz.actionResults.points_awarded} points!</p>
        )}
        <button onClick={quiz.reset}>Try Again</button>
      </div>
    );
  }

  return (
    <div className="my-quiz">
      <div className="progress-bar" style={{ width: quiz.progress + '%' }} />
      <p className="balance">{pointsBalance} pts</p>
      <h3>{quiz.currentQuestion.question}</h3>
      <div className="options">
        {quiz.currentQuestion.options.map((opt, i) => (
          <button
            key={i}
            className={quiz.selectedAnswer === i ? 'selected' : ''}
            onClick={() => quiz.selectAnswer(i)}
          >
            {opt}
          </button>
        ))}
      </div>
      <div className="nav">
        {!quiz.isFirstQuestion && <button onClick={quiz.previous}>Back</button>}
        {quiz.isLastQuestion ? (
          <button onClick={quiz.submit} disabled={quiz.isSubmitting}>
            Submit
          </button>
        ) : (
          <button onClick={quiz.next}>Next</button>
        )}
      </div>
    </div>
  );
}

Hook Reference

Detailed API docs for every hook, organized by category.

Server-Side Actions

When a headless activity hook submits (e.g., quiz.submit()), the server executes all configured on-completion actions atomically. The actionResults object contains the outcomes.

// After quiz.submit() resolves:
const results = quiz.actionResults;

// Points
if (results?.points_awarded > 0) {
  showPointsAnimation(results.points_awarded);
}

// Badges
if (results?.badges_unlocked?.length > 0) {
  results.badges_unlocked.forEach(badge => {
    showBadgeUnlocked(badge.name, badge.icon);
  });
}

// Tier upgrade
if (results?.tier_upgrade) {
  showTierCelebration(results.tier_upgrade.new_tier);
}

// Reward claimed
if (results?.reward_claimed) {
  showCouponCode(results.reward_claimed.code_value);
}

// Challenge progress
if (results?.challenge_progress) {
  showProgressUpdate(results.challenge_progress);
}
Key principle: Actions are configured in the Builder (via on-completion actions) and executed atomically on the server. The headless SDK doesn't need to know about individual actions, it just submits the activity and receives the combined results.

Full Example: Custom Campaign Page

A complete example with a custom quiz UI, points display, badge shelf, and challenge progress, all with your own design system.

import {
  BricqsProvider,
  useEngagementData,
  useQuiz,
  usePoints,
  useTier,
  useBadgesHeadless,
  useChallenge,
  useLeaderboard,
  useReferral,
  useContest,
  useGates,
  useActivityProgress,
  useActivityCalendar,
} from '@bricqs/sdk-react';

const ENGAGEMENT_ID = 'your-engagement-uuid';

function App() {
  return (
    <BricqsProvider config={{ apiKey: 'bq_live_xxx' }}>
      <CampaignPage />
    </BricqsProvider>
  );
}

function CampaignPage() {
  const { getQuizConfig, getComponent, isLoading } = useEngagementData({
    engagementId: ENGAGEMENT_ID,
  });

  if (isLoading) return <LoadingSpinner />;

  return (
    <div className="campaign-layout">
      <main>
        <QuizSection
          config={getQuizConfig()!}
          componentId={getComponent('quiz')!.id}
        />
      </main>
      <aside>
        <PointsCard />
        <BadgeShelf />
        <ChallengeProgress />
        <TopPlayers />
      </aside>
    </div>
  );
}

function PointsCard() {
  const points = usePoints({ engagementId: ENGAGEMENT_ID });
  const tier = useTier({ engagementId: ENGAGEMENT_ID });

  return (
    <div className="points-card">
      <span className="balance">{points.balance}</span>
      <span className="label">points</span>
      {tier.currentTier && (
        <div className="tier" style={{ color: tier.tierColor || '#666' }}>
          {tier.tierName}, {tier.pointsToNext} pts to {tier.nextTierName}
        </div>
      )}
    </div>
  );
}

function BadgeShelf() {
  const { badges } = useBadgesHeadless({ engagementId: ENGAGEMENT_ID });

  return (
    <div className="badge-shelf">
      {badges.map(badge => (
        <div key={badge.code} className={badge.earned ? 'earned' : 'locked'}>
          <img src={badge.icon} alt={badge.name} />
          <span>{badge.name}</span>
        </div>
      ))}
    </div>
  );
}

function ChallengeProgress() {
  const ch = useChallenge({ engagementId: ENGAGEMENT_ID, autoEnroll: true });
  if (!ch.challenge) return null;

  return (
    <div className="challenge">
      <h3>{ch.challenge.name}</h3>
      <div className="progress-bar">
        <div style={{ width: ch.progressPercentage + '%' }} />
      </div>
      <p>{ch.completedObjectives}/{ch.totalObjectives} objectives</p>
    </div>
  );
}

function TopPlayers() {
  const lb = useLeaderboard({
    code: 'main',
    engagementId: ENGAGEMENT_ID,
    limit: 5,
  });

  return (
    <ol className="leaderboard">
      {lb.entries.map(e => (
        <li key={e.rank}>
          <span className="rank">#{e.rank}</span>
          <span className="name">{e.name}</span>
          <span className="score">{e.score}</span>
        </li>
      ))}
    </ol>
  );
}

Migrating from iframe to Headless

1
Keep BricqsProvider

If you already use the React SDK, keep the same provider setup. Headless hooks work within the same context.

2
Replace BricqsEngagement with hooks

Replace <BricqsEngagement /> with useEngagementData() + activity hooks. Build your own rendering layer.

3
Move event handlers to hook results

Instead of onPointsAwarded callback props, read from quiz.actionResults.points_awarded after submission.

4
Use progression hooks for sidebar data

Replace any iframe-based progression displays with usePoints(), useBadgesHeadless(), useChallenge(), and useLeaderboard().

Next Steps