OmniVoke User Guide
A complete guide to using OmniVoke: from your first product to a measured signup pipeline. Covers the dashboard, the nine supported platforms, the attribution model, scheduling, approvals, and the public API.
Table of contents
- What OmniVoke does
- Core concepts
- Getting started
- Products
- Connecting platforms
- Creating and publishing content
- Generation kinds
- Options: audience and destination
- Reading your results (Outcomes)
- Scheduling
- Approvals
- Evergreen recycling
- The public API
- How attribution works
- Troubleshooting and FAQ
1. What OmniVoke does
OmniVoke turns one idea into a measured marketing loop:
Create → Generate → Render → Publish → Attribute → Test → Amplify → Report
You give it a product, an audience, and a topic. It writes the hook, the caption, a short-video script, and a call-to-action (CTA), publishes to the platforms you have connected, and reports which hook and platform actually drove signups.
Two ways to drive it:
- The dashboard (a human): type a topic, hit one button.
- The API (a process): POST the same inputs and get the same pipeline.
Everything a human can do, an API call can do, and both produce the same result.
2. Core concepts
| Term | Meaning |
|---|---|
| Tenant | Your workspace. Everything you create is scoped to it and invisible to other tenants. |
| Product | A thing you are marketing (a SaaS app, a book, a newsletter). Holds the landing-page URL and a brand-voice document. |
| Post (content) | The canonical piece of content: caption, script, CTA, UTM tags, and the hook/variant that produced it. |
| Publication | One publish attempt of one post to one platform, at one occurrence. |
| Platform | A destination: YouTube, LinkedIn, Bluesky, Mastodon, Reddit, Pinterest, X/Twitter, Facebook, or Instagram. |
| Hook | The opening formula (AIDA, PAS, BAB, curiosity gap, contrarian, and more) that the caption opens with. |
| Variant | The A/B identity of a post, carried in utm_content so results are attributable. |
| Attribution | Tracing a click and a signup back to the exact post, hook, platform, and campaign that produced it. |
3. Getting started
- Open the dashboard (
Open workspaceon the landing page). - Add your first product (see Products).
- Connect at least one platform (see Connecting platforms).
- Type a topic and publish.
On a fresh account the publish card shows an inline "add your first product" form, so the first-run path is: add product, connect a platform, publish.
4. Products
A product is what you are marketing. Each product carries:
- Name — shown in generated copy and the post title.
- Landing page URL — the destination your CTA links to, with UTM tags appended.
- Brand voice — an 8-section document (style, tone, keywords, grammar, vocabulary, on/off-tone examples, persona, context) that shapes generated copy.
Managing products
- Add: in the Products section, enter a name and a landing URL and save. The same inline form appears in the publish card when you have zero products.
- Select: the publish card auto-selects your only product. With two or more products, a selector appears above the topic box so you pick before publishing.
5. Connecting platforms
OmniVoke publishes to nine platforms. Each keeps its credentials encrypted at rest (AES-256-GCM). Two connection styles:
- OAuth redirect (YouTube, LinkedIn): authorize through the platform's own flow.
- Key / token paste (everything else): generate a token on the platform and paste it in.
The /setup page (linked from the Connect modal) walks through where to find each key.
| Platform | Auth | Notes |
|---|---|---|
| YouTube | OAuth 2.0 | Google Cloud project + YouTube Data API v3; text-only has no surface, so YouTube publishes as a Short with an attached video. |
| OAuth 2.0 | Posts + analytics products; org analytics requires a company page URN (administrator-gated). | |
| Bluesky | App password | Handle + app password from bsky.app. |
| Mastodon | Access token | Any instance; write:statuses + read:accounts scopes. |
| Script app | Posts to a subreddit you moderate. | |
| OAuth bearer | Board ID required; image pins. | |
| X / Twitter | OAuth (paid tier) | Write access needs a paid tier (Basic or higher). |
| Facebook Pages | Page token | pages_manage_posts; live apps need app review (dev mode works without). |
| OAuth (Business/Creator) | Needs a Business/Creator account linked to a Facebook Page; personal accounts cannot publish. |
See docs/PLATFORMS/*.md for endpoint and credential-mapping detail.
6. Creating and publishing content
The one-button flow:
- Pick a product (auto-selected if you have one).
- Type a topic — or leave it blank and OmniVoke picks one. "Surprise me" fills a random suggested topic.
- Publish.
OmniVoke generates a caption (150-300 words, opens with the hook, ends with a CTA), a 30-second video script, a CTA, and hashtags. It then publishes to your selected destination and shows the result.
The result panel
After publishing you see:
- Caption — the post body, with the tracked link embedded.
- Video script — the 30-second voiceover narration.
- CTA — the single call-to-action sentence.
- Hashtags — the generated tags.
A demo mode badge appears when content was produced by the deterministic mock (no LLM key configured), so you always know whether output is real or placeholder.
7. Generation kinds
Beyond the default caption, OmniVoke can produce other content shapes. These are all reachable from the single generate endpoint (kind parameter):
| Kind | Output |
|---|---|
caption |
Caption + script + CTA + hashtags (the default). |
carousel |
A multi-slide post (LinkedIn document posts). |
thread |
A multi-post sequence. |
hashtags |
A hashtag strategy. |
calendar |
A 30-day themed content calendar. |
Note:
carousel,thread,hashtags, andcalendarare currently exposed through the API and the generate route, not yet as separate dashboard buttons. The dashboard's one-button flow is thecaptionkind.
8. Options: audience and destination
Behind the Options disclosure on the publish card:
- Audience — who the content is for (e.g. "early-stage founders"). Feeds the brand-voice prompt. Defaults to "your target customer" if left blank.
- Destination — where to publish. One of the nine platforms, or
BOTH(LinkedIn + YouTube Short). Defaults to LinkedIn. - YouTube Short — YouTube has no text-only surface, so a Short requires an attached vertical video. When you select YouTube Short, a file picker appears.
Destination determines the UTM source and medium on the tracked link (e.g. LinkedIn → source=linkedin, YouTube Short → source=youtube&medium=short).
9. Reading your results (Outcomes)
The Outcomes section answers "which hook drove the most signups."
Key performance indicators
| KPI | Meaning |
|---|---|
| Signups | Total attributed signups. |
| Cost / signup | Spend per signup (shows "no spend tracked" until spend is recorded). |
| Conversion | Clicks → signups rate. |
| Clicks | Total attributed clicks. |
Deeper breakdowns
- Winning hook — the single hook that drove the most signups.
- Signups by platform — counts per destination.
- Funnel — click → signup → qualified, with drop-off and conversion between stages.
- A/B significance — per-experiment statistical significance (z-score, p-value, winner, required sample size).
- Per-campaign rollup — signups, clicks, spend, conversion, cost-per-signup per campaign, plus cross-promotion opportunities.
10. Scheduling
Scheduling lets posts fire on a recurring cadence without manual clicks. It is driven by cron-style slots plus best-time resolution.
Recurring slots
A slot stores a cron expression, a timezone, a platform, and a nextFireAt cursor. On each scheduler tick the cursor advances off the prior boundary (never "now"), so fire times stay anchored to the schedule with no drift.
- If the scheduler was suspended and falls more than one period behind, it does not backfill a burst. It coalesces to a single fire, logs the skipped boundaries, and resets the cursor.
- The scheduler tick runs on a daily Vercel cron (
/api/scheduler/tick).
Best-time-to-post
OmniVoke learns the best (day-of-week, hour) to publish per platform from past engagement, with Laplace smoothing and a minimum-observations floor. When best-time yields a concrete window it resolves the run to that window; otherwise it falls back to a per-platform default hour (YouTube 17:00, LinkedIn 09:00 local).
Interval syntax
Repeat intervals accept both ISO-8601 durations (P7D, PT12H) and simple units (7d, 2w, 3h, 4m, 1y).
Note: scheduling is currently API-driven (via recurring
ScheduleSlotrecords and the scheduler cron), not yet a dashboard form.
11. Approvals
Approval is a first-class post state, not an afterthought:
- A post can be marked
requiresApproval. A due job for such a post routes to PENDING_APPROVAL instead of straight to publish. - A human approves, rejects, or force-publishes via the approve endpoint.
- Every approve/reject/force-publish transition writes an audit record with the actor, timestamp, and reason. A force-publish records who requested it and why, so the audit trail is real, never a fabricated "scheduler" placeholder.
Approval state is one of the ten ContentStatus values: DRAFT, SCHEDULED, PENDING_APPROVAL, APPROVED, REJECTED, PUBLISHING, PUBLISHED, PARTIALLY_PUBLISHED, FAILED, CANCELLED.
12. Evergreen recycling
A post can be marked to repeat on an interval, so a good piece of content keeps re-publishing without you redoing it.
- Set
repeat+repeatInterval(and optionallyrepeatUntil). - When a completed job is recyclable, OmniVoke enqueues a new occurrence with a fresh idempotency key, anchored to the prior job's
runAt(so the cadence stays stable regardless of publish latency). - Recycling stops at
repeatUntil.
Because each occurrence gets a new idempotency key, the exactly-once guarantee holds unchanged across recycled publishes.
13. The public API
The v1 API mirrors the dashboard: anything you can do by hand, you can script. It is key-gated.
Authentication
API keys authenticate requests. Keys are created and revoked via the keys endpoints; only the SHA-256 hash is stored, and the raw key is returned exactly once at creation. Each key has scopes (content:read, content:write, webhook:manage, key:manage) and a per-minute rate limit.
Content
| Endpoint | Purpose |
|---|---|
POST /api/v1/content |
Create content. Pass either product + audience + topic (generate) or a pre-written body. Optional run: true triggers the async pipeline. |
GET /api/v1/content/[id] |
Read a content item. |
POST /api/v1/content/[id]/run |
Run (publish) an existing content item. |
Keys
| Endpoint | Purpose |
|---|---|
POST /api/v1/keys |
Create an API key (raw key returned once). |
DELETE /api/v1/keys/[id] |
Revoke an API key. |
Webhooks
| Endpoint | Purpose |
|---|---|
POST /api/v1/webhooks |
Subscribe an endpoint to lifecycle events. |
DELETE /api/v1/webhooks/[id] |
Remove a subscription. |
Webhook deliveries are HMAC-signed with a per-endpoint secret (returned once at creation). Deliveries are retried with exponential backoff, and a daily sweep cron re-attempts due failed deliveries across process lifetimes.
Idempotency
Content creation is idempotent. Send an Idempotency-Key header; a replayed request with the same key returns the stored response instead of creating a duplicate.
Example: create and run a post
POST /api/v1/content
Authorization: Bearer <api-key>
Content-Type: application/json
{
"product": "Acme",
"audience": "bootstrapped founders",
"topic": "shipping faster with AI",
"destination": "LINKEDIN",
"run": true
}
The response returns the content id and its status. The publish runs through the same engine as the dashboard button, with the same exactly-once guarantees.
14. How attribution works
Every post's CTA carries a tracked URL built from your landing page plus UTM parameters:
sourceandmedium— derived from the destination.campaign— derived from the product name.content— the A/B variant (the resolvedvariantA/variantBwhen an experiment matches, otherwise the hook-derived variant id).- A
personalization_id— a random per-visitor identifier so attribution stays clean across the landing-page → signup hop.
When a visitor clicks, the tracking snippet reports the click; when they sign up, the signup event lands via /api/events (gated by an ingest key). OmniVoke then attributes the signup back to the exact hook, platform, and campaign.
Referral/affiliate codes are also captured alongside signups, so referral-driven signups can be attributed to their source.
15. Troubleshooting and FAQ
I clicked Publish and nothing happened.
Check that you have (a) at least one product and (b) at least one connected platform. On a fresh account the publish card shows an inline "add your first product" form.
The result has a "demo mode" badge.
No LLM key is configured, so OmniVoke fell back to a deterministic mock. Output is placeholder, not real generation. Set OPENROUTER_API_KEY (and the tier models) to enable real generation.
YouTube won't publish.
YouTube has no text-only surface. Select YouTube Short and attach a vertical video.
X/Twitter or Instagram won't publish.
X/Twitter API write access needs a paid tier; Instagram needs a Business/Creator account linked to a Facebook Page. Personal Instagram accounts cannot publish via the API.
My platform shows "Connected" but publishes fail.
Re-authorize the connection. OAuth tokens expire and may need a refresh; the connection status reflects the stored token state.
I see PARTIALLY_PUBLISHED.
Some platforms succeeded and some failed. OmniVoke records per-platform run statuses independently and aggregates them; retry re-arms only the non-terminal (failed) runs.
What does "occurrence" mean?
A post can publish multiple times (scheduling or evergreen recycling). Each publish is one occurrence; a new occurrence gets a fresh idempotency key so a recycled post never double-publishes.
Where are my credentials stored?
Encrypted at rest (AES-256-GCM) in the credential vault, keyed by VAULT_KEY. Only the SHA-256 hash of API keys is stored; raw keys are shown once.
Is my data isolated from other tenants?
Yes. Every domain record is scoped by tenantId, including best-time engagement buckets, so one tenant's data can never leak into another tenant's scheduling or analytics.