Developer Docs

Start the 7-day free trial & submit results

For all 6 live IQs — GTMIQ, SalesIQ, ProductIQ, AITransformIQ, UXIQ, TariffIQ (5 capability diagnostics and 1 specialist diagnostics) — and any future IQ. Everything runs through @gemiq/hub-sdk — no direct Stripe, Supabase, or HubSpot calls from your IQ.

1. Install the SDK (auto-pull)

The Hub owns the SDK. Your IQ pulls it on every build so you always ship against the current contract.

  1. Copy packages/hub-sdk/pull-hub-sdk.mjs from the Hub repo into your IQ at scripts/pull-hub-sdk.mjs.
  2. Add scripts to your IQ's package.json:
{
  "scripts": {
    "pull:hub-sdk": "node scripts/pull-hub-sdk.mjs",
    "prebuild":     "node scripts/pull-hub-sdk.mjs"
  }
}

Run node scripts/pull-hub-sdk.mjs once locally. It writes src/lib/hub.ts. Never edit that file — it's regenerated on every build.

2. Initialize the client

// src/lib/hub-client.ts
import { createHubClient } from "@/lib/hub";

export const hub = createHubClient({
  hubOrigin: "https://gemiq.globaledgemarkets.com",
});

Because the Hub sets its auth cookie on .globaledgemarkets.com, every IQ subdomain sees the same session — no token passing.

3. Gate the assessment on session + subscription

Call this at the entry point of the assessment (or any paywalled page):

const status = await hub.subscription.check();

if (!status.authenticated) {
  return hub.redirectToLogin(window.location.href);
}

if (!status.active) {
  // Not subscribed and not trialing — send them to checkout.
  await hub.subscription.startCheckout("gemiq_complete_monthly", {
    successUrl: window.location.origin + "/resume?sid={CHECKOUT_SESSION_ID}",
    cancelUrl:  window.location.href,
  });
  return; // browser navigates to Stripe
}

// status.active === true → let the assessment run.

status.active is true for both active and trialing Stripe states.

4. Start the 7-day free trial

Add a Start 7-day free trial button next to your existing subscribe CTA. Pass trial: true:

await hub.subscription.startCheckout("gemiq_complete_monthly", {
  successUrl: window.location.origin + "/resume?sid={CHECKOUT_SESSION_ID}",
  cancelUrl:  window.location.href,
  trial: true,
});

Use gemiq_growth_monthly or gemiq_complete_annual for the other plans. Card is required up-front; the subscription auto-converts on day 7. Stripe sends the reminder email 3 days before conversion automatically.

Trial ships one free assessment across any IQ — enforced by the Hub, not by your IQ. A trial assessment is scored (score and tier are always shown), but the submit response returns report_locked: true: show the score and tier, and hold back the full report until the plan starts. Submission history re-computes report_locked, so the report unlocks automatically once the trial converts.

5. Resume page after Stripe returns

Stripe redirects back to your successUrl with ?sid=<checkout_session_id>. The webhook usually lands within a second but can lag a few. Poll until active:

// /resume route
const status = await hub.subscription.waitUntilActive({ timeoutMs: 15000 });
if (status.active) {
  router.replace("/start");
} else {
  showRetryButton();
}

6. Submit results

At the end of the assessment:

await hub.results.submit({
  email: user.email,
  assessment_key: "gtmiq", // one of: "gtmiq" | "salesiq" | "productiq" | "aitransformiq" | "uxiq" | "tariffiq"
  score,
  tier,          // canonical 5-tier scale, lowercase: "reactive" | "developing" | "defined" | "advanced" | "optimized"
  dimensions,    // { [dimensionKey]: number }
  detail: {
    // IQ-specific rich payload — stored verbatim, mapped to gem_* HubSpot properties
    // by the Hub's registry entry for this IQ.
  },
  metadata: { first_name, last_name, company },
  report_url: "https://.../report.pdf", // shown in internal notification email
});

The Hub handles all of the following — you do not:

  • Dedupe (10-minute window per email + IQ)
  • DB insert into submissions
  • HubSpot contact upsert with gem_* properties
  • HubSpot Lead creation: Warm on every submit, Hot when score ≥ 80
  • Internal notification email to info@globaledgemarkets.com and alexr@globaledgemarkets.com
  • Retry queue on HubSpot failure
  • Trial assessment counter increment

7. Handling the trial limit (402)

Once a trialing user consumes their one free assessment, hub.results.submit() throws with status === 402 and body.error === "trial_limit_reached". Prompt an upgrade:

try {
  await hub.results.submit(payload);
} catch (e: any) {
  if (e.status === 402 && e.body?.error === "trial_limit_reached") {
    // Trial exhausted — upgrade to full subscription (no trial flag).
    await hub.subscription.startCheckout("gemiq_complete_monthly", {
      successUrl: window.location.origin + "/resume?sid={CHECKOUT_SESSION_ID}",
      cancelUrl:  window.location.href,
    });
    return;
  }
  throw e;
}

Optional UX polish using status:

const status = await hub.subscription.check();

if (status.trialing) {
  // Show "Trial — 1 free assessment" badge in your header
}
if (status.trial_exhausted) {
  // Swap the primary CTA to "Upgrade to continue"
}

9. Central manifest — brand, pricing, deep links

The Hub publishes a single manifest at /api/public/manifest that every IQ should treat as the source of truth for brand tokens, pricing, deep links, and the assessment registry. The manifest is also committed to GitHub at src/lib/hub/manifest.json so IQ builds can pin it.

Build-time pull (recommended)

The updated pull-hub-sdk.mjs now pulls both the SDK and the manifest on every build, and fails the build if your IQ's local manifest is ahead of the Hub's:

↓ SDK      https://raw.githubusercontent.com/.../sdk.ts
↓ manifest https://raw.githubusercontent.com/.../manifest.json
✓ wrote src/lib/hub.ts
✓ wrote src/lib/hub-manifest.json (v1.10.0)

Use it in your IQ:

import manifest from "@/lib/hub-manifest.json";

// Brand tokens straight from the Hub
document.documentElement.style.setProperty("--gem-mint", manifest.brand.colors.mint);
document.documentElement.style.setProperty("--gem-navy", manifest.brand.colors.navy);

// Pricing — never hard-code
const monthly = manifest.pricing.plans.find(p => p.interval === "month");

Runtime polling — live updates without a redeploy

Subscribe to changes so brand, pricing, and deep-link updates propagate to already-loaded IQ sessions:

import { createHubClient } from "@/lib/hub";
import initial from "@/lib/hub-manifest.json";

const hub = createHubClient({ hubOrigin: initial.hub.origin });

const stop = hub.manifest.watch(
  { intervalMs: 5 * 60_000 },  // 5 min; server sends 304 when unchanged
  (next, previous) => {
    console.log("Hub manifest changed", previous?.version, "→", next.version);
    applyBrandTokens(next.brand);
    refreshPricingUI(next.pricing);
  },
);

// stop() on unmount if needed

The endpoint sets a strong ETag and Cache-Control: max-age=60, stale-while-revalidate=600, and responds with 304 when the client's If-None-Match matches — polling is effectively free.

Manifest shape

{
  version: "1.10.0",
  etag: "\"1.10.0-<hash>\"",
  served_at: "<ISO timestamp>",
  hub:   { origin, docs_url, sdk_source, manifest_source, repo },
  brand: { name, fonts, colors, logos, usage_rules },
  pricing: {
    currency,
    trial:     { days: 7, assessments_included, card_required },
    guarantee: { days: 14, type: "money_back" },
    one_time:  { id, name, amount: 179, lookup_key },
    plans: [{ id, name, tier, amount, interval, assessments_included, lookup_key }]   // tier: "growth" | "complete"
  },
  tracks: {
    capability: { label, blurb },
    specialist: { label, blurb }
  },
  assessments: [{ key, name, url, track }],   // track: "capability" | "specialist"
  deep_links: {
    signup_trial_monthly, signup_trial_quarterly, signup_trial_annual,
    buy_single_assessment, login, portal
  }
}

GitHub sources of truth

  • Playbook (v1.5 — source of truth) — PLAYBOOK.md — 5 capability diagnostics and 1 specialist diagnostics, 8–9 dimensions, canonical 5-tier model, pricing
  • SDK — packages/hub-sdk/sdk.ts
  • Manifest — src/lib/hub/manifest.json (semver — bump on every change)
  • Puller — packages/hub-sdk/pull-hub-sdk.mjs (copy into each IQ)
  • Repo — GlobalEdgeMarkets/gemiq-unified-hub

Reference

SDK surface

  • hub.subscription.check() → CheckStatus
  • hub.subscription.startCheckout(lookup_key, { successUrl, cancelUrl, trial? })
  • hub.subscription.waitUntilActive({ timeoutMs?, intervalMs? })
  • hub.subscription.openPortal(returnUrl)
  • hub.results.submit(payload)
  • hub.results.history()
  • hub.profile.get() / hub.profile.update(patch)
  • hub.manifest.get({ etag? }) — one-shot fetch with 304 support
  • hub.manifest.watch({ intervalMs? }, onChange) — live polling
  • hub.redirectToLogin(returnTo, mode?)

Stripe lookup keys

  • gemiq_growth_monthly — Growth, $149/mo, 3 assessments (the first 3 different ones taken); a 4th returns 402 plan_limit_reached
  • gemiq_complete_monthly — Complete, $249/mo (default)
  • gemiq_complete_annual — Complete, $2490/yr
  • gemiq_single_assessment — $179 one-time, 14-day money-back guarantee

CheckStatus shape

{
  authenticated: boolean;
  active: boolean;          // true for "active" OR "trialing"
  trialing?: boolean;
  trial_exhausted?: boolean;
  user?: { id, email };
  subscription: {
    status, lookup_key, current_period_end, cancel_at_period_end,
    stripe_subscription_id,
    trial_ends_at, trial_assessments_used, trial_assessment_limit
  } | null;
}

Canonical tier vocabulary

The only accepted values for tier, lowest to highest. Submit these exact lowercase strings — anything else has to be guessed at on the Hub side.

reactive  →  developing  →  defined  →  advanced  →  optimized

Full integration guide including HubSpot property registration and legacy user import lives in INTEGRATING.md in the Hub repo. The suite-level source of truth — 5 capability diagnostics and 1 specialist diagnostics, the 8–9 dimension standard, the canonical five-tier model and pricing — is PLAYBOOK.md (v1.5).