What we access, where it lives, and what happens on cancel.
Tideline is small enough that we can name the actual seams — the integration list, the relational store behind DATABASE_URL, the admin allow-list, and the contact-form row that drives deletion — instead of waving at a SOC2 badge we don't have.
What we actually read from each connection.
Every integration below is wired into the home-page stack strip and listed again on /channels. The column to the right of each row names the data shape it sends to the agent — nothing more, nothing less.
- Amazon
- Amazon Seller Central
- Helium 10
- Linnworks
- Skubana
Seller Central ad-spend (cents), Sponsored Products / Sponsored Brands set names, and your target ACOS or CPA caps; the buyer-message body the CX flow drafts a reply to. Inventory rows from Helium 10 / Linnworks / Skubana land in the reorder-drafts queue.
- Shopify
- Shopify
- Klaviyo
- Gorgias
Shopify orders, products, inventory, and one customer-identifier per row paired with the matching Klaviyo profile + Gorgias ticket. Klaviyo flow + Gorgias macro library IDs. Nothing from a payment provider tokenises the card — we read the order line, not the charge.
- Meta
- Meta Ads
- Klaviyo
Ad-account spend (cents), campaign and ad-set names, creative asset references, and the audience tags you target. Klaviyo identity records join back to Meta DM message bodies the inbox flow drafts a reply to.
- TikTok
- TikTok Shop
- Gorgias
TikTok Shop order rows (line items, ship-to city, ETA) and TikTok DM message bodies the inbox flow drafts a reply to. Gorgias ticket threads open for any DM that escalates past brand voice.
One relational store, scalar values only.
Postgres behind DATABASE_URL. One source of truth — every tenant-bearing row carries a scalar workspaceId, every required scalar carries a @default(...) so a deploy stays idempotent against a live DB.
Scalar cents and text only — no images, no videos, no media files of any kind.
Every row carries workspaceId: String (defaulted for now — section 4 explains why).
ContactMessage (name + email + message + createdAt, written by the public POST /api/contact) lands in the company inbox at POLSIA_COMPANY_EMAIL through the Polsia email proxy — never a third-party SaaS.
One allow-list today. A real session check later.
No SSO, no OAuth, no SCIM, no MFA — those are not wired. Today the gate is an explicit CSV of admin emails read from the env.
The requireAdmin() helper in src/lib/admin.ts:41-66 reads the ADMIN_EMAILSenv CSV and produces a 403 in production unless the caller's email is on the list. In non-production it permits any request so the admin list is reachable locally. When better-auth installs, requireAdmin will swap to auth.api.getSession(...) + a role check; the helper already exposes the hook.
No third-party staff read your data. The founder is the only human who ever touches the database.
Request it once. Every row is removed.
Honest about today's posture: until better-auth installs, every row is shared across visitors — the tenant isolation is enforced at the application boundary by the single workspaceId defaulting to "default".
A deletion request today is a ContactMessage row written by POST /api/contact: name + email + message + createdAt. A human reads it, removes the tenant-scoped rows (VoiceProfile, dashboard tiles, rules, guardrails, voice-profile samples), and replies on the same thread.
Once better-auth installs, the same intent becomes an in-app action gated by the allow-list from §3 — the schema swap turns tenantKey into userId and cascade-deletes the rows the user owns.
The literal samples you uploaded, no embeddings, no residue.
Honest about the schema: VoiceProfile stores the plain text of the 3..5 replies you submit during /onboard. There is no Embedding table — the copy below cites the field by name.
prisma/schema/voice-profile.prisma:9-22 defines VoiceProfile { samples String[] } — the literal 3..5 prior replies uploaded during /onboard, plus tone, an optional name?, the workspaceId, and the tenantKey scalar. There is no @relation anywhere — no Embedding / VectorStore row lands on the database, full stop.
On deletion the row and its scalar workspaceId are removed. Nothing else references VoiceProfile (the schema header comment states this explicitly), so the row cannot leave an orphan behind.
Five trust questions, answered.
Each row below points back to one of the five sections above.
The help center answers the wiring questions.
The help center walks through connection setup, the digest format, and the way the agent reacts to drift per channel. Pricing scales with the stores you connect and the reply volume you handle.