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