Developer Docs

Start the 7-day trial & submit results

For TariffIQ, ReadinessIQ, UXIQ, TechServicesIQ, 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_professional_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_professional_monthly", {
  successUrl: window.location.origin + "/resume?sid={CHECKOUT_SESSION_ID}",
  cancelUrl:  window.location.href,
  trial: true,
});

Use gemiq_professional_quarterly or gemiq_professional_annual for the other terms. 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.

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: "tariffiq", // or "readinessiq" | "uxiq" | "techservicesiq"
  score,
  tier,          // lowercase: "emerging" | "developing" | "established" | "advanced" | "leading"
  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_professional_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.0.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.0.0",
  etag: "\"1.0.0-<hash>\"",
  hub:   { origin, docs_url, sdk_source, manifest_source, repo },
  brand: { name, fonts, colors, logos, usage_rules },
  pricing: {
    currency, trial: { days, assessments_included, card_required },
    plans: [{ id, name, amount, interval, lookup_key }]
  },
  assessments: [{ key, name, url }],
  deep_links: { signup_trial_monthly, signup_trial_annual, login, portal }
}

GitHub sources of truth

  • Playbook (v1.4 — source of truth) — PLAYBOOK.md — 6 capability IQs, 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_professional_monthly — $99/mo
  • gemiq_professional_quarterly — $279 / 3 months
  • gemiq_professional_annual — $990/yr

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;
}

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 — six capability IQs, the 8–9 dimension standard, the canonical five-tier model and pricing — is PLAYBOOK.md (v1.4).