tellatribe

Enter the access code to read the project documents.

tellatribe Docs Status: All modules built and live · Oct 19: Creator Campaigns Part B shipped (briefs, applications, deals, verification → bounties, brand balance + Stripe funding, disputes, reports) on top of the tax-identity gate and annual statements · awaiting owner inputs: tellatribe Stripe account (payments + Connect + funding webhook), HealthyApp webhook secret, Ayrshare key

Reference · DECISIONS-FOR-REVIEW.md

Decisions made without you — for review

Running list of calls I made on my own judgment while building "until all modules are built." Each line says what I decided and where it landed. Change any of them and I'll propagate. The 15 answered questions are in BUILD-PLAN.md §5 and are not repeated here.

Build & process

  1. Phase branches merge to main without asking once a phase's gate passes (overrides the usual "ask before merging" for this project only, per "make a decision… keep going").
  2. Migrations renumbered at merge when parallel agents collide (0011 publishing, 0012 admin_flags, 0013 links). Supabase keys by version, so numbers are never reused.
  3. Deploys are prebuilt from the workspace root (scripts/deploy-app.sh) because the Vercel project root directory can't be set from this machine. Switching to git-connected deploys later is a one-time dashboard change.
  4. Test timeouts raised to 240 s in integration suites so runs under heavy host load don't false-fail.

Product

  1. Redirect path is /go/[code] on each product's own domain (HealthyApp's /r/[token] was taken). Short-link domain registration is still yours.
  2. Click cookie: HttpOnly, SameSite=Lax, 90 days — matches the HealthyApp program's cookie window; the SDK reads the window from the API so other products can differ.
  3. HealthyApp seeded rates: 25% for 12 months (L1) + 5% lifetime (L2), 30-day hold, workspace minimum payout. Placeholder until you set real terms in Admin → Programs.
  4. Stripe webhook secret and payout account are env/config, not code; until the tellatribe Stripe account exists the Payouts screen shows an honest "payout account not configured" state rather than a fake run.
  5. Test-mode keys write mode='test' rows that never enter the payable set, so you can exercise the full flow on live surfaces without creating liabilities.
  6. Over-refunds are refused (refund_exceeds_conversion) rather than driving a commission negative past the original.
  7. Suspended affiliate's commissions go to flagged (held visibly in the queue), not silently dropped.
  8. Media pipeline (renditions) runs on Vercel functions with sharp + ffmpeg-static, bounded batches every 2 min; moves to a worker only if it hits the 250 MB function limit (agent R reports).
  9. Collateral bucket is private, 500 MiB cap, browser uploads go direct to Supabase Storage (TUS) — nothing passes through a Vercel function.
  10. HealthyApp integration is on a branch in vera-wellness (affiliate-coach-integration) and never merged by me. SDK is vendored (copied), not published to npm.
  11. Supabase Auth: magic links only, redirect allow-list limited to the two live app domains + localhost.
  1. Mode 3 (teleprompter) ships in v1 as a "Record it" sheet — resolved script, read time, Copy script — not an in-browser camera. The phone's own camera app records; the sheet is the prompter. Full teleprompter UI is Phase 6c.
  2. Composer offers Instagram / Facebook / TikTok / LinkedIn at launch; X is not in the composer. X's open-balance + fee gating (decision 11) is enforced at the connection step, so it appears only once that flow is mounted.
  3. Disclosure check runs on the final edited caption, server-side; warns once, then "Post anyway."
  4. Wall billboard states beyond the brief: under-minimum ("rolls forward"), approved-but-no-payout-details (→ Set up payouts), negative net with the reason, and a three-step first-run inside the billboard.
  5. "Join to promote" cards link to /join?program=<id>; the shell's join flow doesn't read that param yet — small follow-up in the shell.
  6. Connect social account is a stub until the Ayrshare key exists (buttons show a toast). Publish-queue Retry is not offered to affiliates; failed posts show the reason and only queued ones can be cancelled.
  7. Ref links render as /go/CODE by rewriting core's /r/ in one place; the spoken form says "slash go slash".
  8. Next payout date shown = first of next month (workspace payout_schedule is monthly for HealthyApp).
  9. A test affiliate exists in the live database — Test Affiliate, code TESTAFF, email test-affiliate@affiliate.coach, enrolled in HealthyApp, no sponsor — so the live portal can be verified and screenshotted without waiting for a real signup. Suspend or delete it from Admin → Affiliates when real affiliates arrive (its code TESTAFF is permanent per the immutable-code rule).
  10. Joining a second product from the Wall is one confirmation screen (/wall/join/<program>: rate, hold, exact terms version, "Join and accept the terms") — no admin approval step, same code and link, enrollment recorded with the current terms version like a first signup. Admin can still suspend the enrollment.

HealthyApp integration (branch affiliate-coach-integration in vera-wellness, not merged)

  1. SDK is vendored into lib/affiliate-coach/ (copied, header names the source commit) — no npm publish, no new runtime dependency.
  2. Click cookies are widened to .healthyapp.coach (like the session cookie) because a returning owner is paywalled on their tenant subdomain and a host-only apex cookie would be lost. Host-only on *.vercel.app/localhost.
  3. /go/ is added to HealthyApp's middleware public-path list — without it every anonymous click would be bounced to sign-in before the redirect ran.
  4. Metadata lands only in startPlatformSubscription (the one subscription-mode checkout on the owner's own Stripe account). Card setup and Connect checkouts are untouched; client_reference_id is never used.
  5. Affiliate links must point at healthyapp.coach/go/CODE, not the marketing site (gethealthyapp.coach is a separate deployment). The product's redirect_origin is already https://healthyapp.coach.

Media renditions (packages/media)

  1. 17 rendition cells across 7 platforms (Instagram feed 4:5 + 1:1, reel, story, carousel; Facebook feed + 1.91:1 + reel; TikTok; LinkedIn ×3; X 16:9 + 1:1; YouTube Short) plus a generic source and cover. Images are JPEG everywhere (not WebP — what lands in Photos and every network accepts); video is padded to the aspect, never cropped; sources longer than a cell's cap fail that cell with a plain message before any CPU is spent.
  2. PDFs get a generated cover card (title + page count), not a first-page render — no PDF rasteriser is available in the serverless build. The PDF itself is served as its own rendition.
  3. Failed renditions are not auto-retried (the failures are deterministic: too long, unreadable); re-enqueue from the admin regenerates them. The schema has no error column yet, so the reason is only in the worker log — a small follow-on migration is recommended.
  4. Storage: one private bucket collateral, 500 MiB project ceiling, applied live (the bucket itself had to be created through the Storage API — supabase config push pushed the limits but did not create it); /api/cron/renditions runs every 2 minutes with a bounded batch and the ffmpeg binary traced into that one function only.
  5. ffmpeg comes from @ffmpeg-installer/ffmpeg (per-platform optional packages), not ffmpeg-static, and pnpm installs linux-arm64 native packages beside the Mac's (supportedArchitectures in pnpm-workspace.yaml) — because deploys are prebuilt on this Mac and Vercel runs the API on linux-arm64. Nothing changes for a future git-connected deploy.
  6. Links are never deleted and only their label can change — a URL that is out in the world keeps working. Every enrolment mints one default link automatically; labelled links are the affiliate's own.
  7. Per-link counters are computed, not stored: a labelled link counts clicks carrying its slug; the default link owns every click no labelled link claims (incl. Studio's collateral-tagged clicks). "Visitors" = distinct device per UTC day (the IP hash is salted daily, so cross-day uniqueness is impossible by design).
  8. Short links are hidden until SHORT_LINK_DOMAIN is set (the domain is yours to register); the recruiting link uses https://tellatribe.com/join/CODE until NEXT_PUBLIC_PORTAL_ORIGIN says otherwise.

Teleprompter (Mode 3, v1.5)

  1. Recordings save to the phone, not to Studio (share sheet on iOS → Photos; download elsewhere). Uploading takes into Studio is a later item; the seam is in place.
  2. Pace comes from the rendered script at 150 wpm, speed 0.8×–1.4×, 3-2-1 countdown, soft mark at 60 s, hard stop at 3 min; the spoken-disclosure paragraph is bolded and the scroll pauses 1.5 s the first time it reaches the reading band.
  3. Burn-in is chosen before recording (standby switch, default off, remembered per affiliate); when the browser can't composite it falls back to a plain take and says so.
  4. In-app browsers (Instagram/Facebook/TikTok/LinkedIn) get an "open in Safari/Chrome" card instead of a broken camera — they don't expose the camera to web pages.
  5. Route is /studio/record/<asset> (the plan said /studio/<asset>/record); refusals render a reason card with a back link rather than redirecting.

Admin second half (Collateral · Payout runs · Reports · Audit · Settings)

  1. Publishing a collateral version requires three things the schema only documents: a disclosure block, a ticked claims attestation (recorded with who/when), and valid tokens; scripts must also carry {{spoken_disclosure}}. Rolling back to an older version passes the same gate.
  2. Payout release is three explicit clicks — Assemble → Check funding → Release — each audited; an empty assembly opens no run. Funding instructions (decision 9) show when the payout account is short.
  3. Support/sender email and the four fraud thresholds live in workspaces.branding.settings (jsonb) rather than new columns, to avoid a migration collision mid-wave; a proper column migration is the better long-term home. The chargeback rule is still a constant in the webhook handler and the Settings page says so.
  4. "Revenue" in reports = attributed live conversion amounts (never gross, never test mode). Cohorts key on first attributed payment per product × customer.
  5. Renditions in the admin come straight from the media package's SQL half (@affiliate-coach/media/enqueue); the native pipeline runs only in the API cron. Admin uploads go browser → signed Storage URL, never through a function.

Publishing wiring (Connect → post)

  1. Migration 0014 lets a revoked social connection return to connected (0011 forbade every exit from revoked, so Disconnect → Reconnect could never work).
  2. Connect state is an HMAC token (30 min, bound to affiliate + network) signed with CONNECT_STATE_SECRET, falling back to the service-role key — no new secret required. Ayrshare return URL to whitelist: https://tellatribe.com/studio/connect/return.
  3. X appears in Connected accounts only when offered (program has X on and the affiliate's balance covers the fee) — absent otherwise, no greyed nag.
  4. The Composer's preview and the worker's outgoing payload come from the same preview(), asserted by a test; nothing can post that wasn't shown.

Found while demoing

  1. A conversion timestamped in the same second as its click is treated as "before the click" and goes unattributed. Stripe reports whole seconds, our clicks carry milliseconds, so a checkout that completes within the click's second (or any clock skew between Stripe and our server) would lose attribution. Fixed Oct 5: the window check now tolerates a conversion up to 60 s before its click (CLOCK_SKEW_TOLERANCE_MS), with unit tests.
  2. Focused caption tiles overlap their own title on the Wall (the caption's first line renders as display text behind the title). Fixed Oct 5: the focused poster line clamps to two lines.

Creator Campaigns (decided by Jonathan, Oct 14 — recorded here for completeness)

  1. Open calls visible to every eligible affiliate on the wall.
  2. 10% brand-side fee on flat fees only; gifting free.
  3. tellatribe is the payer of record (one 1099 per creator).
  4. Instant payout creator-paid at 1%; standard run free.
  5. Drafts auto-approve after 10 business days of brand silence, on by default.
  6. No platform follower floor; the brief decides.

Payout tax compliance (Oct 19)

  1. tellatribe files 1099-NEC, never 1099-K. It is payer of record (decision 57); affiliates receive transfers, not card sales. Overrides, flat campaign fees and gifted product (at fair market value) all count toward the same total. Threshold is a workspace setting seeded at $2,000 for 2026 (the post-2025 federal threshold, inflation-indexed), not a constant.

  2. Tax identity gates payouts. Stripe Express collects W-9 / W-8 and TIN-matches at onboarding; an affiliate whose tax details are not verified has their balance held rather than paid. Rationale: paying an unverified TIN triggers 24 % backup withholding, which we will not operate. Launch is US-only for payouts; non-US recipients (W-8 on file, no 1099) are enabled country by country.

  3. Stripe Connect is the launch payout rail; PayPal Payouts is a later second rail. PayPal is cheaper per payment ($0.25 flat domestic) and needs no recipient onboarding, but its standard API collects no tax forms and files no 1099s, requires an application PayPal can deny, and returns unclaimed email payouts after 30 days. Add paypal to the payout-method enum only once our own W-9/W-8 collection exists.

  4. Stripe's Connect 1099 product does the filing (e-delivery free; $2.99 IRS e-file, $1.49 direct state file, $2.99 mailed, per Stripe's published page). Calculation method is payments by payout date; the admin gets a calendar-year paid-by-recipient report (paid minus recovered reversals) and runs it monthly so January reconciles to Stripe. Someone must click File in January — Stripe does not file on its own.

  5. Every affiliate gets a calendar-year earnings statement in the portal regardless of threshold, and product copy never characterises income or gives filing advice ("consult your tax advisor").

  6. Three Vercel projects (API, portal, admin), one monorepo, one database. Different audiences and risk: the portal is public and mobile-first; the admin requires membership + MFA and never shares a domain or session with the portal; the API hosts the Stripe webhooks, the redirector and the crons and must keep running through any UI deploy. Each has its own domain, env, deploy history and rollback. Consequence: the Stripe restricted key is read by all three, so it is set once as a Vercel team-level shared env var on sales-boss and attached to the three projects; the two webhook secrets live only on the API project.

Built Oct 19 (tax identity): migration 0015 adds affiliates.tax_status / tax_country / tax_status_synced_at, mirrored from account.updated and live syncs; the payout run and the admin preview skip tax_identity_missing; Reports gains Paid by recipient · tax year (CSV + drill); the portal's Payout method shows the tax-identity state and Earnings → Payouts lists downloadable annual statements per calendar year.

Creator Campaigns build (Oct 19 → )

  1. Gift value is recorded on the deal, not as a commission row. A non-cash value must never enter a payout run; the tax report adds verified gift value per recipient to the 1099 total.
  2. A bounty is a commission row of kind bounty that names its deal_task_id (one per task), born approved (or pending when the brief sets a hold of 1–7 days); its reversal (post deleted inside the retention window) names the same task.
  3. Brand money is a per-brand balance ledger (brand_balance_entries, append-only): funding in, escrow held at acceptance, bounty and the 10 % platform fee out at release. Balance = Σ entries; free to spend = balance − reserved.
  4. Creator metrics come from the connected network's analytics through the existing aggregator, with manual entry as an unverified fallback. No new OAuth apps for v1; the app-review path for first-party demographics stays in the PRD's "later".
  5. The platform fee is charged to the brand balance at release, snapshotted on the deal (platform_fee_bp) so a later price change leaves old deals alone.
  6. Verification matches the post the creator links plus the brief's required tags/handles, via the aggregator's post history where connected; unverifiable formats are brand-approved with proof.
  7. Brands fund their balance through Stripe Checkout on the payout account (card or ACH), minimum $100, processing fees paid by the brand. So the payout account now accepts payments as well as sending them; a funding webhook (/api/stripe/funding) on that account credits the ledger once per Checkout session. The manual bank top-up stays as the fallback for the house brands.
  8. Eligibility is evaluated in the app, not the database, against creator_profiles, so an ineligible brief can be shown dimmed with its one reason (PRD B.6) rather than hidden.
  9. Non-Stripe publishers integrate through POST /v1/conversions (click id or ref code + idempotency key; refunds as reversals), which already exists and is processor-agnostic; no-code connectors (Shopify app, WooCommerce plugin, webhook relay) are a later phase, not Part B.
  10. Brand balance arithmetic: Σ entries is the free balance. Holds are negative entries, so the engine writes an escrow_release for the consumed hold alongside every bounty + platform_fee; a hold and its spend never both subtract. Migration 0018 redefines brand_balances.reserved_cents as holds − releases over open deals (floored per deal); brandBalance() returns freeCents = Σ entries, reservedCents, balanceCents = free + reserved.
  11. Migration 0018 widens two vocabularies the engine needs: commission_events.source gains campaigns (0005's check would have rejected the contract's affiliate_coach.source = 'campaigns' and rolled back every verification) and fraud_flags.kind gains post_deleted (0008 removed the catch-all manual on purpose).
  12. The escrow hold covers the flat fee only; the 10 % platform fee leaves the free balance at release (decision 71). A brand can therefore read slightly negative on fees until it tops up; the funding sweep funds nothing until then.
  13. The fee a deal pays, in order: the admin's explicit amount → the brand's counter the creator agreed to → the creator's bid → the brief's flat fee → zero. Gift value/description default from the brief's gifting (value_cents or max_value_cents, description); a gift with a value needs a description. platform_fee_bp is snapshotted at 1000.
  14. Acceptance enrolls only against published terms. When the brief's program has no terms_version_id, accept() refuses with program_has_no_terms rather than writing an enrollment with no compliance record.
  15. Facts for eligibility: wall sales = the creator's attributed purchase conversions in the workspace; an exclusivity runs from the last post (or the deal's creation while nothing is posted) + exclusivity_days; a brief conflicts when its own exclusivity categories or its eligibility categories intersect an open exclusivity; the reason names the brand ("you have an exclusivity with HealthyApp until Nov 2"). creatorFacts takes an optional productId to scope enrolledInProduct.
  16. Dispute decisions: release → the deal goes back to verified (all tasks complete) or in_progress; refund → deal cancelled, remaining hold released; split → a bounty of splitCents on the first task without one (approved, fee charged), the rest released, deal cancelled. An appealed dispute closes with its second decision (one appeal, PRD B.11).
  17. One rights grant per deal, opened at the first verified task, starting at that post's posted_at; rightsReminders stamps reminder_sent_at and writes the audit row (mail is not wired).
  18. Sweeps: verificationSweep looks only at tasks with a post_url or a publish_job_id; a deleted post inside the usage window moves an approved bounty to flagged (flag_reason manual) and only flags (fraud_flags) a paid one, once per task.
  19. Quality score v1 weights: engagement vs tier benchmark 40, reach/followers 20, comments-per-like 15, 30-day growth 15, audience-country match 10. An unknown input leaves the average and lowers the ceiling by 12, so an account with no data lands at 40 ("check"), never "good" by default.
  20. Task path: reviewDraft('approve') leaves the task at draft_approved; markPosted walks draft_approved → awaiting_post → posted_unverified in one call (no-draft tasks from not_started), and re-posts a failed_verification task with the fixed link.
  21. The funding webhook resolves its workspace from the Checkout session's metadata.workspace_id (checked against the table), falling back to the only workspace when exactly one exists, like /connect; metadata.product_id is written on the event row only when that product exists in that workspace — so a mis-metadata'd session is still ledgered (ignored:missing_ids) instead of failing the composite FK.
  22. A funding event that carries account is refused as connected_event_rejected (400) but still written to stripe_webhook_events — the exact inverse of /connect's platform_event_rejected guard; the two endpoints never read the same event two ways.
  23. ACH top-ups are two events, one entry: checkout.session.completed with payment_status = 'unpaid' is ledgered ok:pending and writes no balance entry; async_payment_succeeded writes it. A paid completed followed by an async_payment_succeeded for the same session funds once (brand_balance_entries.stripe_ref unique → ok:duplicate).
  24. async_payment_failed and charge.refunded are audit-only (audit_log actor_kind webhook, actions brand_funding_failed / brand_funding_refunded, object products); the admin reverses a refunded top-up by hand with refundFunding after checking what the brand still owes.
  25. /api/cron/campaigns runs the seven sweeps per workspace with each step isolated, reporting counts, a per-step errors list and the post-lookup mode: aggregator when AYRSHARE_API_KEY is set, else url_only (found = typed post_url present). In aggregator mode an Ayrshare error for one task falls back to url_only for that task — the aggregator being down must never read as "post deleted"; the paid-partnership label is always null because /history does not expose it.
  26. packages/campaigns in the API branch is a contract stub (header "STUB — replaced by the engine stream at integration"): the exact §1.3/§1.5 signatures the API calls, with only recordFunding implemented (on-conflict on stripe_ref). The integrator drops it for the engine stream's package; all API logic lives in apps/api/lib/{funding,campaigns-cron,post-lookup}.ts.
  27. .env.example is force-added (the repo ignores .env*, yet AGENTS.md names the file as the list of every variable an app reads); it documents STRIPE_FUNDING_WEBHOOK_SECRET beside the other API secrets with no values.
  28. (admin) Campaign queue items open the campaign detail, not the affiliate drawer. dispute, failed_verification, unfunded_deal and weak_applicant link to /campaigns/[id]#section; loadException returns null for them, so the drawer code stays untouched.
  29. (admin) "Pending settlement" on the balance strip is inferred from the event ledger: a stripe_webhook_events row with endpoint funding and type checkout.session.completed in the last 5 days whose session id has no brand_balance_entries.stripe_ref — no new column, no admin write.
  30. (admin) A creator's own deal on the brief being evaluated never counts as a clashing exclusivity. evaluateEligibility gets the facts minus that campaign's deals; otherwise every hired creator would read ineligible on their own campaign.
  31. (admin) The gift column on Paid by recipient also lists gift-only recipients (a deal verified in the year with no payout sent) and the 1099 threshold test uses paid + gift, since the gift alone can cross it (decision 61/67); the Stripe Tax-reporting total will be short by the gift value, and the page says so.
  32. (admin) Weak applicant = an open application whose creator has a weak profile and no good/check one; a creator with mixed words is not queued (the ranking already shows the word).
  33. (admin) Exclusivity windows for eligibility run from the deal's start until exclusivity_days after the campaign's last post window (or the deal's creation when no window is set) — the admin's own read; the engine's creatorFacts is the authority at integration.
  34. (admin) Unverifiable = podcast, newsletter, live or custom formats, or the newsletter/podcast/other platforms; only those show "Approve as delivered", everything else waits for the sweep (decision 72).
  35. (admin) The brand-funding Checkout line item is a one-off price_data named "tellatribe balance · " with metadata.kind = 'brand_funding', so the funding webhook can refuse sessions that are not top-ups.
  36. (admin) The report's attributed sales use the campaign window published_at → closed_at|now and only creators with a live (non-cancelled) deal on the campaign; the campaign must have a program_id, otherwise the report says "no program" rather than inventing attribution.

Portal stream (creator side), Oct 19: 97. Eligibility facts are built in the portal from the creator's own my_* rows (profiles, enrolment from the context, completed deals, wall sales, exclusivities from open deals' campaigns) and handed to the engine's evaluateEligibility; the service role reads only the exclusivity terms of campaigns behind open deals, which my_campaigns withholds once a brief closes. 98. The open-call tile's tint is the product mark colour mixed 45 % into the ground (color-mix), so the brand reads on the tile without product colour touching chrome; no mark colour → the slate tint. 99. Campaign and deal server actions return { ok, message } instead of throwing, because Next masks thrown messages from server actions in production and a CampaignError's message is the sentence the sheet must show. 100. Post-for-me from a deal records the post link lazily: enqueue first, then markPosted(external_url, publishJobId) the moment the job carries a URL; until then the task shows "A post is queued from here" with a "Record the link when it is live" control, and Copy & post stays available. No engine function links a job to a task before it posts. 101. The post window is enforced in the portal's own preflight (lib/studio/window.ts), joined to the network preflight in previewPublish and re-checked in enqueuePublish, rather than in the publishing package — the package knows nothing about campaigns. 102. Draft files go to the collateral bucket at {ws}/deals/{deal}/{task}/{ts}-{filename} through a signed upload URL minted server-side after my_deal_tasks confirms the task is the creator's; the storage path, not a URL, is what submitDraft records. 103. Bounty rows take their words from my_deal_tasks.bounty_commission_id (the 0016 footer's route) plus my_deals / my_campaign_deliverables, with campaign title and product name from workspace-scoped service reads; a bounty with no reachable task still renders as "Piece for a brand · verified … · pays …" rather than failing. 104. The Studio tab count adds deals whose next action is the creator's (a funded deal awaiting signature, or an in-progress deal with a task in a creator status); an unfunded deal's not-started tasks never count. 105. "Something wrong?" is offered while a deal is in progress or delivered, and for 7 days after a verified/paid deal's completion; one appeal per decided dispute. Inside the engine's openDispute/appealDispute; the portal only gates the controls. 106. The reputation line calls the engine's creatorReputation and hides on any failure or zero deals; the portal computes nothing of its own there.

Still yours (not decided, blocking nothing)

  • Create the tellatribe Stripe account → STRIPE_SECRET_KEY, STRIPE_CONNECT_WEBHOOK_SECRET, STRIPE_FUNDING_WEBHOOK_SECRET; enable card + ACH payments (brand top-ups) and Connect → Tax reporting (1099-NEC) per the Stripe guide Parts 1, 5 and 6.
  • Create the HealthyApp revenue-account webhook → HEALTHYAPP_WEBHOOK_SECRET; set products.stripe_account_id; set the three AFFILIATE_COACH_* env vars on the HealthyApp Vercel project; merge affiliate-coach-integration (full checklist in that branch's INTEGRATION.md).
  • Ayrshare API key (AYRSHARE_API_KEY on the portal + API projects; whitelist the return URL above) — the only thing between Connect and live posting; domain for short links (SHORT_LINK_DOMAIN); real program rates.

Rendered from DECISIONS-FOR-REVIEW.md. Edit the source, not this page.