What is implemented
This release includes public product pages, a login-protected demo dashboard, a private SQLite workspace per session, and a separate private-file contact inbox. Drafts, reviews, planned schedules, simulated publications, channel state, settings, and activity are saved server-side. Reloads retain saved changes during the 24-hour session. The demo API below is implemented; it is not a social publishing API.
No OAuth authorization endpoint, live social integration, MCP server, issued API key, published SDK, payment collection, production credential vault, enterprise SSO, or external analytics integration is enabled. The seven sample channels are demonstrations, not connected accounts. Any broader platform coverage is a product direction, not an inventory of enabled integrations. Example engagement belongs to seeded sample data; simulated new publications do not invent impressions. A working demo and policy pages are not proof of platform approval.
Start an isolated demo session
- Open demo login. Use email
[email protected]and passwordExplorePulseFlow!, also displayed on that page. These are intentionally public demo credentials, not personal-account credentials. - Read and explicitly acknowledge that the session lasts 24 hours and only sample data should be entered. Login is not an invitation request, a paid signup, or consent to connect a platform.
- Enter the dashboard. A new session gets its own seeded workspace. Another visitor using the same credentials does not receive your workspace. An already-valid session can resume its existing workspace.
- Make a sample change and save it. A reload preserves saved changes; closing a tab does not delete them. Unsubmitted editor changes and local file playback references are not saved workspace data.
- Before leaving permanently, open Settings and export your workspace if wanted. Reset replaces the sample data but keeps the original expiry. Logout or deleting demo data removes the workspace and ends the session; a subsequent login starts fresh.
The essential __Host-pulseflow_demo production cookie is HttpOnly, SameSite=Lax, and Secure, with a fixed 24-hour expiry. Its host-only prefix prevents parent-domain cookie injection. Local development uses pulseflow_demo without the Secure flag. Its opaque token is hashed at rest. Cookies must remain enabled to resume the session. There is no recovery based on the shared demo email. Do not enter real personal data, confidential client content, real passwords, or platform tokens.
A complete dashboard workflow
- Overview summarizes your saved workspace, not a shared public dataset.
- Content lets you create, search, filter, edit, duplicate, and delete drafts. Choose a sample channel and content format. Local video selection previews MP4/WebM up to 100 MB; only its planning metadata is saved.
- Approvals handles review requests. Ready drafts move to review; approve them or request changes with a note. This is a workflow simulation in one session, not a multi-user approval or role-enforcement service.
- Calendar displays saved schedules with week navigation and filters. Only approved, ready posts on connected sample channels can be scheduled. Use a future time and read the time-zone label before saving; the stored instant is UTC, while the workspace time zone determines calendar display.
- To test a publication outcome, explicitly choose the simulated publishing action on an approved or scheduled record. A scheduled time does not trigger a live or background publication. A published demo record is not editable; duplicate it to start a new draft.
- Channels changes sample connection state. Disconnecting returns affected in-review, approved, and scheduled work to draft and clears schedules, without deleting its content or published history. Reconnecting does not restore approvals or schedules.
- Analytics filters and exports the selected demonstration data. Seeded metrics are explicitly examples; newly simulated publications have no invented engagement.
- Activity shows filterable saved events. It is bounded operational history, not a tamper-evident audit ledger.
- Settings saves workspace name, display name, IANA time zone, and Sunday/Monday week start; it also provides workspace JSON export, reset, and deletion.
Editing reviewed, approved, or scheduled content invalidates its approval and returns it to draft with no schedule. If another tab changes the workspace, a stale save receives a conflict instead of overwriting newer data. Keep your pending edits, reload the current workspace, then reapply the intended change. Read visible error messages; an unsuccessful request is not a saved action.
Current demo HTTP API
The API uses the browser’s session cookie, not an API key or workspace ID supplied by the client. Authentication and private responses use Cache-Control: no-store. Mutations require Content-Type: application/json, a trusted exact Origin, and compatible Sec-Fetch-Site metadata. JSON request bodies are limited to 64 KiB. The production origin is SITE_URL; development permits matching local development origins only. Do not disable these checks to run a cross-origin client.
| Endpoint | Request and result |
|---|---|
POST /api/demo/session | {email, password, consent: true}. Valid public credentials create or resume a session, set the cookie, and return {ok: true, data: {redirectTo: "/dashboard"}}. |
DELETE /api/demo/session | Send JSON {}. Deletes the current workspace and clears its session/cookie. Returns {ok: true, data: {signedOut: true}}. |
GET /api/demo/workspace | Returns {ok: true, data: {workspace, viewer, expiresAt}}. The workspace includes its revision, settings, channels, posts, and activity; viewer contains the demo name/email. |
PATCH /api/demo/workspace | Send {revision, action} with the most recently read revision. Applies one validated action atomically and returns the new snapshot in the success envelope. |
DELETE /api/demo/workspace | Send {revision}. Deletes the current workspace/session/cookie and returns {ok: true, data: {deleted: true}}. |
GET /api/demo/export | Downloads a raw DemoWorkspace JSON attachment, not the API success envelope. Contains only this workspace, never cookies, token hashes, or contact records. |
Other success responses use {ok: true, data: ...}; errors use {ok: false, error: "...", code?: "..."}. Handle status and ok before showing success. Invalid credentials or an absent/expired session receive 401; invalid input receives 400; stale revisions receive 409. Origin failures are rejected. Rate limits return 429; full session capacity or unavailable storage returns 503. A failed persistence operation never silently falls back to browser-only or in-memory success.
Login is capped at 60 attempts globally per 10 minutes; writes are capped at 120 per session per minute. These limits persist across restarts and do not trust client-supplied IP headers. The shared capacity is 500 active sessions, with at most 100 posts and the newest 200 activity events per workspace. They are public-demo resource bounds, not production service entitlements.
Read and update from a same-origin client
After logging in through the UI, the browser supplies the cookie and origin metadata automatically. This example changes sample settings using the latest revision; it intentionally throws on any failed request.
const current = await fetch("/api/demo/workspace", { cache: "no-store" });
const result = await current.json();
if (!current.ok || !result.ok) throw new Error(result.error || "Read failed");
const { workspace } = result.data;
const saved = await fetch("/api/demo/workspace", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
revision: workspace.revision,
action: {
type: "update_settings",
settings: { ...workspace.settings, name: "Sample launch desk" }
}
})
});
const update = await saved.json();
if (!saved.ok || !update.ok) throw new Error(update.error || "Save failed");The source-of-truth TypeScript definitions are in src/lib/demo/types.ts: DemoSnapshot, DemoWorkspace, DemoMutation, and DemoAction. Server-rendered dashboard routes read the session through getDemoSnapshot() from @/lib/demo/server; unauthenticated visitors are redirected to login. These routes and API responses must never enter public page caches.
Workspace action contracts
Every action below is nested inside {revision, action} for PATCH. Post IDs refer only to records in the current cookie’s workspace. A successful mutation increments the revision; reset does not rewind it.
| Action and fields | Observable behavior |
|---|---|
{type: "create_post", post} | Creates a draft; incomplete caption or consent may be saved for later. |
{type: "update_post", postId, post} | Replaces editable input. Reviewed/approved/scheduled work becomes draft and loses its schedule. Published records cannot be edited. |
{type: "duplicate_post", postId} | Creates a new draft without metrics, publication, or schedule; TikTok privacy and consent require a fresh decision. |
{type: "delete_post", postId} | Removes that record and records an activity event. |
{type: "request_review", postId} | Ready draft → in_review. |
{type: "approve_post", postId} | in_review → approved. |
{type: "request_changes", postId, note} | in_review → draft, preserving a required 1–500 character review note. |
{type: "schedule_post", postId, scheduledAt} | approved → scheduled. Requires a valid future ISO timestamp and rechecks readiness and channel connection. |
{type: "unschedule_post", postId} | scheduled → approved; clears scheduledAt. |
{type: "publish_demo", postId} | approved/scheduled → published, with current publication time and null metrics. Explicit simulation only. |
{type: "set_channel", platform, connected} | Toggles a sample channel. Disconnect invalidates that channel’s unpublished approvals and schedules without removing content or unrelated channels. |
{type: "update_settings", settings} | Saves the full settings object: name, displayName, valid IANA timeZone, and weekStartsOn (0 for Sunday, 1 for Monday). |
{type: "reset_workspace"} | Replaces workspace contents with fresh relative-date samples, retaining workspace identity and session expiry while increasing revision. |
Post input
post contains title (1–120 characters), caption, platform, format (text, image, video, or carousel), media, and tiktok. Supported platform IDs and caption limits are:
tiktok: 2,200 characters.instagram: 2,200 characters.x: 280 characters.linkedin: 3,000 characters.youtube: 5,000 characters.facebook: 5,000 characters.threads: 500 characters.
media is {kind, name, duration}; kind is none, sample-video, sample-image, or local-video, and duration is seconds or null. It is planning metadata, not an upload URL. The API does not fetch remote media. For TikTok, supply {privacy, comments, duet, stitch, disclosure, musicConsent}; other platforms use tiktok: null. Privacy is null until chosen, then PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, or SELF_ONLY. Disclosure is none, own-brand, branded, or both. Interaction options start false.
Review, scheduling, and simulated publication require a connected sample channel and complete ready content. TikTok requires video with duration greater than zero and at most 180 seconds, an explicit privacy choice, and music consent. Branded or both disclosure cannot use SELF_ONLY. Client controls aid input, but the server enforces these boundaries.
A publish is an explicit decision
- Select an original video or inspect the supplied six-second silent sample. Local files stay in the browser. The demo accepts MP4/WebM up to 100 MB; TikTok readiness requires at most 180 seconds. Production limits must come from the target platform.
- Edit the caption and hashtags. Review the sample creator identity and playback before making a consent decision.
- Choose an audience yourself; never inherit a public default. The demo shows three TikTok enum values, but a live application must restrict the list to the current creator-info response.
- Choose interaction permissions and commercial disclosures. Nothing is affirmatively preselected. Paid partnerships cannot be private; a conflict requires the user’s own correction.
- Confirm music rights and applicable branded-content terms. Save a draft, request review, approve it, then choose a future time to save a demo schedule or explicitly simulate publishing.
The saved calendar record does not upload or publish anything. Local playback references are temporary; planning metadata persists with the workspace. A live implementation must revalidate permission and creator limits before a scheduled job begins. TikTok does not become a native scheduling API simply because the application has a calendar; scheduling belongs to a future application worker.
For a live service, media must use temporary HTTPS transit, be automatically purged within 30 days of ingestion, and be sent only after explicit consent. An API acceptance response is not a successful publication: poll the documented platform status endpoint or verify signed platform webhooks, then show processing, published, failed, or cancelled state accurately. Platform processing may take several minutes.
TikTok Direct Post: live-integration requirements
video.publish authorizes creator-approved Direct Post operations. user.info.basic is used only when needed to identify the consenting creator. video.upload is a separate upload-to-inbox capability, not a substitute for Direct Post permission; it should not be requested for a Direct Post-only flow.
The live composer must retrieve the latest creator information, display the creator’s nickname, honor privacy_level_options and max_video_post_duration_sec, and disable interactions the creator has disabled. Privacy and comment/duet/stitch controls have no affirmative defaults. If the creator cannot post, stop rather than queueing around the restriction.
Commercial disclosure starts off. Turning it on requires “Your brand,” “Branded content,” or both. Your brand is labeled promotional content; any third-party branded content is labeled paid partnership. Only-you visibility is incompatible with branded content. The user must consent to TikTok’s Music Usage Confirmation and, for branded content, the Branded Content Policy.
Unaudited API clients are restricted to SELF_ONLY, have a small test-user cap, and must satisfy TikTok’s current requirements before public sharing. This demo is not an approved Content Sharing partner. TikTok requires a product for authentic creators and a broad audience; an internal-only account-upload utility is not an acceptable Direct Post use case. Do not submit the authenticated demo as if it were a working TikTok-connected service.
For server-held original media, PULL_FROM_URL must use a domain or prefix whose ownership is verified. For media on a creator’s device, follow the documented FILE_UPLOAD flow. Preserve original content and never add promotional watermarks. See TikTok’s Content Sharing Guidelines.
Meta: Facebook, Instagram, and Threads
| Permission | User-facing purpose |
|---|---|
pages_show_list | Let the user select a Page they can manage. |
pages_read_engagement | Read permitted Page metadata needed by the selected workflow. |
pages_manage_posts | Create and manage only Page posts the authorized user directs. |
instagram_basic | Identify the connected professional Instagram account in the Facebook Login flow. |
instagram_content_publish | Publish user-approved media to that professional account. |
threads_basic | Identify the consenting Threads account. |
threads_content_publish | Publish the user’s explicitly approved Threads content. |
These scopes do not authorize unrelated profiling, collection of friends’ data, or posting to arbitrary accounts. Instagram Login uses its own instagram_business_* permission family; choose the actual login product and document its permissions instead of mixing scope names across products. Request only what the enabled user-facing feature needs.
Meta app review, business verification where required, and advanced access must be completed separately. Graph API v25.0 is the stated integration review target, not an approval badge. Configure the Data Deletion Instructions URL as https://pulseflow.arieltolome.com/data-deletion. This instructions page is not a signed-request deletion callback; do not configure it as one. Review Meta permissions, Instagram Platform, and Threads API before implementation.
Google and YouTube
https://www.googleapis.com/auth/youtube.upload is the intended scope for creator-directed video uploads. Read scopes such as youtube.readonly, or yt-analytics.readonly for a separate analytics feature, must be requested incrementally only when that feature is implemented and visibly needed. An upload permission is not consent to unrelated Google data processing.
The privacy policy contains the mandatory Limited Use commitment. Google API data must not be sold, used for personalized advertising, used for credit decisions, or passed into general-purpose model training. OAuth verification and the YouTube API compliance audit are distinct processes; approval of one does not establish the other. Uploads from unverified projects may be restricted to private visibility.
Provide a working disconnect path and direct users to Google connected apps for independent revocation. Review the Google API Services User Data Policy and YouTube developer policies.
LinkedIn OAuth 2.0
w_member_social permits posting on behalf of the authenticated member. Optional openid, profile, and email belong to an explicit OpenID Connect sign-in feature, not an automatic request for every publisher. Organization posting requires the relevant approved product, w_organization_social, and verification that the member can act for the selected organization. Additional organization-read or administration permissions must be justified by an actual feature.
Use the authorization-code flow, validate a single-use state value and exact redirect URI, and exchange the code only on the server. A visible profile or company logo does not establish permission to post. Document/carousel publishing must use the supported current media and Posts APIs, subject to the account and product’s access. See Share on LinkedIn and LinkedIn authentication.
X: OAuth 2.0 and OAuth 1.0a
For OAuth 2.0 Authorization Code with PKCE, the posting workflow uses tweet.read, users.read, and tweet.write. Request media.write only for media upload and offline.access only when the user chooses persistent scheduling that needs refresh tokens. Do not request direct-message, follow, or like permissions for a publishing-only product.
OAuth 1.0a does not use the OAuth 2.0 scope strings. It uses app-level permission settings such as Read and write, with a server-side consumer secret, user access token/secret, and signed requests. “Read, write, and Direct Messages” is unnecessary for this workflow. The current demo does not perform either OAuth flow or verify a real X connection.
Threads must preserve part ordering, report partial failure, and never silently duplicate earlier parts. API access tiers and quotas still apply; neither OAuth method bypasses them. Read X’s OAuth 2.0 scope reference and OAuth 1.0a user tokens.
TikTok root verification hook
Obtain the actual verification filename and contents from the TikTok Developer Portal. Never invent a verification token. Next.js serves files placed in public/ at the root: a portal file named tiktok…html belongs directly there, unchanged, before building and deploying.
Alternatively, the included root handler accepts TIKTOK_VERIFICATION_FILE and TIKTOK_VERIFICATION_CONTENT as server environment variables. Filenames must begin with tiktok, contain only letters, digits, underscores, or hyphens after that prefix, and end in .html or .txt. Only the exact configured filename returns the exact configured content; everything else is a real 404. Empty configuration returns no verification document.
Use one method, not both for the same path. Restart the server after changing runtime verification values. Check the exact root URL over public HTTPS for HTTP 200 and byte-for-byte contents, then request verification in TikTok. The hook is implemented; domain ownership is not verified by this website build. DNS and TLS must point to the deployed application first.
One origin for every public reference
SITE_URL is the single canonical-origin setting. Its configured value is https://pulseflow.arieltolome.com/. Set it to an absolute HTTP(S) origin without credentials, a path, query, or fragment. Public deployment must use HTTPS. Canonicals, OpenGraph URLs, robots, sitemap, and review URLs are derived from it; internal navigation is origin-relative. Rebuild and restart after changing the origin. The request-origin checks must match that same deployed origin.
- App homepage:
https://pulseflow.arieltolome.com/ - Demo login:
https://pulseflow.arieltolome.com/login - Privacy URL:
https://pulseflow.arieltolome.com/privacy - Terms URL:
https://pulseflow.arieltolome.com/terms - Data Deletion Instructions URL:
https://pulseflow.arieltolome.com/data-deletion
For future live OAuth integrations, register one exact callback per provider only after the corresponding server-side authorization-code exchange, PKCE/state checks, consent records, and token custody exist and have been tested. No /api/oauth/* callback routes are implemented by the current demo login. Changing a domain also requires updating each platform’s allowlist; an environment update cannot modify external dashboards.
Run and operate this website
Use Node.js 22.22 or newer with node:sqlite support; the Docker runtime uses Node 24. Dependencies are locked in package-lock.json. The production build runs strict TypeScript checks, ESLint, and Next.js; fonts are bundled locally. This is a Node server application, not a static export or an ephemeral-filesystem deployment.
npm ci
# Local development (non-Secure development session cookie):
SITE_URL=http://localhost:3000 npm run dev
# Production: set the real HTTPS origin for both build and runtime.
# Provision private persistent directories before starting.
SITE_URL=https://your-host.example npm run build
SITE_URL=https://your-host.example \
PULSEFLOW_DATA_DIR=/private/pulseflow/dashboard \
CONTACT_INBOX_DIR=/private/pulseflow/contact npm run startThe production process must sit behind correctly configured HTTPS. Production session cookies are Secure; a plain-HTTP production preview is not an end-to-end demo-login verification. Use local development for HTTP exploration, or verify the actual production HTTPS origin. Keep SITE_URL consistent across build, runtime, proxy routing, and verification.
Two separate private data stores
PULSEFLOW_DATA_DIR owns demo.sqlite and SQLite sidecar files. The local default is .data/dashboard/demo.sqlite; the Docker deployment path is /data/dashboard/demo.sqlite. Mount a persistent writable directory, not only the database file, because SQLite uses WAL sidecars. Demo directories use private 0700 permissions and files 0600. CONTACT_INBOX_DIR is independently configured, defaults to .data/contact, and uses /data/contact in Docker. Neither store belongs under public/, in source control, in published artifacts, or behind a download route.
Preserve both mounts across process restarts and container replacement. Run a single application instance with its local persistent storage unless you deliberately redesign the shared-storage and admission controls; copying a SQLite file to multiple replicas does not make a shared service. A missing or unwritable data directory must produce an error, not an in-memory fallback or a false save confirmation. Session/workspace writes use atomic SQLite transactions and revision checks.
Sessions expire 24 hours after creation. Expired records are removed at startup and on access, plus an hourly cleanup while the process runs. Reset does not extend expiry. Logout or explicit deletion removes the current workspace/session. Keep the host clock correct. If the process is stopped, cleanup resumes at startup. Manage logs, disk protection, and any backups under the privacy notice; do not restore deleted sessions into service. Application record deletion is not a promise of forensic erasure of storage media.
Contact requests: record first, acknowledge second
POST /api/contact requires a matching Origin and Content-Type: application/json. Its body has name, email, optional company, topic (Access, Support, Privacy, or Security), message, consent: true, and the empty honeypot website: "". It accepts messages of 20–5,000 characters and at most 16 KiB total. Only a durable private-file write receives HTTP 201 with a requestId. Invalid input, origin mismatch, quota, and write failures are non-success responses.
No email service is configured or implicitly used. The operator must monitor the private inbox and reply using the supplied address. Contact records and receipts never appear in the public demo, its activity view, or its exports. The demo credentials do not authorize inbox administration. Set the same CONTACT_INBOX_DIR for the server and this local operator command:
node tools/inbox.mjs list
# Read a specific request; the command marks it read:
node tools/inbox.mjs show REQUEST_UUID
# Permanently delete a resolved request after review:
node tools/inbox.mjs delete REQUEST_UUIDThe UUID comes from the private list output or successful form receipt; it is not an authentication token. CLI output contains personal information and belongs only in a private operator terminal. Delete inquiries within 90 days after resolution unless a documented legal retention exception applies. Contact intake’s process-local 30-request/hour limit resets on restart; public hosting needs appropriate edge-level abuse protection.
Deployment acceptance
Before serving public traffic, verify the operator identity and support/privacy mailboxes, persistent directory ownership, DNS, HTTPS, request-origin configuration, and private-cache behavior. Exercise two isolated sessions; saved edits across reload and restart; invalid and expired cookies; stale-revision conflicts; export boundaries; reset; logout; and deletion. Confirm write failures remain failures. A homepage health check alone does not demonstrate authentication or persistence. Monitor storage, capacity, cleanup, and the private contact inbox without logging cookies, passwords, or request bodies.
Use the real TikTok portal file to verify domain ownership when needed. Before social-platform app review, separately implement and verify live authorization, creator consent, token custody, delivery status, and deletion requirements. The current authenticated demo is not that connected service. See security boundaries and deletion instructions for the current/future distinction.