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.
- Copy
packages/hub-sdk/pull-hub-sdk.mjsfrom the Hub repo into your IQ atscripts/pull-hub-sdk.mjs. - 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.comandalexr@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"
}8. Deep-link straight to signup + trial
Marketing pages and blog CTAs can send visitors directly into the Hub signup with the trial preselected:
https://gemiq.globaledgemarkets.com/auth?mode=signup&trial=1&plan=monthly
https://gemiq.globaledgemarkets.com/auth?mode=signup&trial=1&plan=annualAfter signup, the Hub auto-initiates Stripe checkout with trial_period_days: 7 on the chosen plan.
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 neededThe 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()→CheckStatushub.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 supporthub.manifest.watch({ intervalMs? }, onChange)— live pollinghub.redirectToLogin(returnTo, mode?)
Stripe lookup keys
gemiq_professional_monthly— $99/mogemiq_professional_quarterly— $279 / 3 monthsgemiq_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).
