Privacy Policy
What ClipSpell stores, why it stores it, where it goes, how long it stays, and which parts of all that are not built yet.
1. Who runs ClipSpell
ClipSpell is operated by Adrian Borislavov Tolev, an individual established in Bulgaria rather than a registered company, of ul. Vetrusha 5, Pavlovo, Sofia, Bulgaria. In this policy that person is “we”.
Privacy questions, and any request about your own data, go to clipspell@yahoo.com.
ClipSpell is operated from Bulgaria, so the law that applies is the General Data Protection Regulation (EU) 2016/679 together with Bulgaria’s Personal Data Protection Act. The supervisory authority you can complain to is the Commission for Personal Data Protection (Комисия за защита на личните данни), 2 Prof. Tsvetan Lazarov Blvd., Sofia 1592, Bulgaria, kzld@cpdp.bg. Complaining to them costs you nothing and does not require you to ask us first.
There is no data protection officer and no Article 27 representative, and neither is required here. A DPO is mandatory under Article 37 only for a public authority, for large-scale systematic monitoring, or for large-scale processing of special-category data, and none of those describes this service. An Article 27 representative is for a controller established outside the EU; this one is inside it.
The legal basis for each use described below:
- Running the service — Article 6(1)(b), performance of a contract. Your email address and password hash, your workspace, your projects, scripts, renders and the credit ledger. All of it exists because you asked for an account and asked it to make videos; without it there is no service to perform. Consent is deliberately not cited for any of this, because consent you could withdraw while still expecting the product to work would be a fiction.
- Security and abuse prevention — Article 6(1)(f), legitimate interests. The sign-in throttle, the IP address recorded against a session, and the server logs in section 3. The interest is keeping accounts from being taken over by password guessing; the data is the minimum that makes a rate limit work, and section 9 says how long each part is kept.
- Records we must keep — Article 6(1)(c), legal obligation. Ledger rows retained after an account closes, where accounting or tax law requires them. Nothing else is held on this basis.
- Connecting a social account — Article 6(1)(b), and the platform’s own consent screen. The OAuth grant you give YouTube, TikTok or Instagram is a permission you hand to them, on their screen, and you can withdraw it there or here. What we then store under it — the sealed tokens and the account name — is contract performance: it is what posting on your behalf requires.
- A review you choose to write — Article 6(1)(f) to keep it, Article 6(1)(a) to show it. Keeping what you sent us is a legitimate interest: hearing from the people who use the product, which is why you were asked. Showing your words and the name you chose on the home page is consent, and only consent: it happens only if you ticked the box that says so, which is never ticked for you, and removing the review in Settings withdraws it at once.
Nothing else described on this page relies on consent as its legal basis, and none of it is profiling or automated decision-making with legal effect under Article 22. A review is read by a person before it is shown, never judged by a model.
2. How this page was written
This is not a template. Every claim below describes the ClipSpell codebase as it stands on the date at the top of this page: the database schema is the inventory of what is collected, the provider registry is the list of what leaves the machine, and the publishing adapters are the list of what a platform is asked for.
Where the truthful answer is “that is not implemented yet”, this page says so plainly rather than describing a control that does not exist. Sections 4, 11 and 14 in particular contain gaps stated as gaps.
3. What we store
One row per database table, because that is the honest granularity: if a column exists and something writes it, it belongs here.
users- Your email address, an optional display name, an optional avatar URL, and a password hash produced by scrypt at N=16384 — the password itself is never stored. Also the time your email was verified, when you were last seen, and created/updated timestamps. An mfa_enrolled_at column exists so that enabling multi-factor sign-in is not a migration; multi-factor sign-in is not implemented.
sessions- A SHA-256 hash of your session token, its expiry, and the time it was revoked. The token itself exists only in the cookie in your browser. Also the IP address you signed in from, where it can be honestly determined — an address behind a proxy that strips the header, or a loopback address, is left empty rather than guessed at, so the column is often null. The user_agent column is declared and nothing writes it: your browser string is not recorded against a session.
workspaces- Workspace name, URL slug, IANA timezone, default language, credit balance, and an optional monthly credit cap.
workspace_members- Which people belong to a workspace, their role, and who invited them.
notifications- What happened in a workspace that somebody should know about: a video finishing or failing, a publication succeeding, failing or going unconfirmed, a series pausing itself, a connected account the platform stopped accepting. Each row holds a short title, an optional one-line body, a link to the thing it is about, and whether it has been read. A notice about YouTube names the platform, never the channel, so nothing here outlives disconnecting it. Addressed to one person when it is their own work and to the whole workspace otherwise. Removed automatically after ninety days by the worker’s hourly sweep.
invitations- An address somebody invited to a workspace, the role offered, who sent it, when it expires and whether it was accepted or withdrawn. The invitation link is not stored: the table holds a SHA-256 of it, so the link exists only in the email. Accepted and withdrawn rows are kept rather than deleted, because who was invited to a workspace and by whom is the record of how somebody got access to it.
projects- Everything you asked for: title, the topic you typed, target platform, aspect ratio, language, target duration, template, tone, quality mode, caption style, voice choice, whether real photos may be used where they fit — and, if you supplied your own script instead of having one written, that script text. Plus a series id and, for a video a series made, a lower-cased copy of that series' brief.
script_versions- Each generated script document, the provider id and model that produced it, the version of the prompt used, the estimated duration and the word count. Versions are kept rather than overwritten, so regenerating does not destroy the previous result.
storyboards- Aspect ratio, style directive, generation seed and colour palette per storyboard.
scenes- Per scene: the narration text, the visual prompt or stock query, any on-screen text, camera move, crop, transition and music ducking, timings, the word-level timings measured against the recorded audio, state, failure reason, and credits spent.
assets- One row per stored file, with the provenance needed to answer a licensing question later: storage key, MIME type, size, dimensions, duration, content hash, origin, provider id, model, prompt, negative prompt, generation seed, source URL, licence and attribution text, for a licensed photo its credit (the file, its title, author, licence and page), the provider request id, and an original_filename column for uploaded files.
video_versions- The canonical timeline handed to the renderer, its content hash and duration.
renders- Storage keys for the output file, thumbnail and caption file; width, height, frame rate, size and duration; the full quality-control report and whether it passed; render time and failure reason.
jobs- State, progress, current stage and stage message, the job input, its result, the credit estimate and the actual spend, an error kind, the message shown to you, an operator-only error_detail visible in the admin job view, and a support error id.
job_steps- Per stage: attempt number, state, provider, model, cost, credits spent, and timings.
queue_messages- The queue payload for work waiting to run, its attempts, lease holder, deduplication key and last error.
credit_transactions- An append-only ledger of every credit movement: event type, signed amount, the balance after it, what caused it, and — for an administrative adjustment — who made it and why. There is no update or delete path.
credit_reservations- Credits held for work whose final cost is not yet known, the amount finally settled, an idempotency key, and expiry.
provider_usage- What an operation cost us at a provider: provider id, model, operation, token counts or units, our estimated and actual cost in millionths of a currency unit, latency, the provider’s request id, and whether the result was cached.
social_connections- One row per connected platform account. See “Connected accounts, tokens and disconnecting” below for the full field list, including how the tokens are protected.
publications- One row per attempt to publish one rendered file to one connected account: platform, state, scheduled time and the timezone you chose it in, the caption and hashtags, the privacy value, the made-for-kids declaration, for a post made straight to TikTok whether you allowed comments, duets and stitches and whether you disclosed it as promoting your own brand or as branded content, for a YouTube or Instagram post your answer, where you gave one, to whether it promotes your own brand or a brand paid for it, the platform’s submission and post ids and URL — for YouTube, deleted as section 8 describes, with the time they were deleted — attempt count, last error, and timestamps.
analytics_snapshots- The numbers a platform reports back about a post, re-fetched on a schedule and kept as a series rather than overwritten: views, likes, comments, shares, saves, reach, impressions, watch time, average watch time and, only where the platform reports it, a completion rate. Also the platform’s own post id, copied so a snapshot can be read without the publication row. Nothing here is derived or estimated — a figure the platform does not report is null, not calculated. For YouTube each reading is deleted once it is 28 days old, so a post’s series reaches back that far and no further, and all of them are deleted when the channel is disconnected, when you close your account, or 28 days after the post could last be read (section 8).
series- A standing brief that makes videos on a schedule: its name, the niche you wrote, the platform, aspect ratio, language, duration, template, tone, quality and voice, the times of day and weekdays and IANA timezone it runs on, which connected accounts it posts to, whether auto-publish is on, for each TikTok account it posts to directly the audience you chose and your answers on comments, duets, stitches and commercial content, for each YouTube channel it posts to, the visibility you chose for its videos, and its own state — including the reason it paused itself if three runs failed in a row.
series_runs- One row per scheduled slot: when it was due, which project and job it produced, its state, and — where it failed — the failure reason and a support error id, or the reason a slot was skipped without being attempted.
password_reset_tokens- A SHA-256 hash of a reset link, never the link itself, with its expiry and the moment it was redeemed — a link works exactly once. Also the IP address and browser user-agent the request came from, so that “who asked to reset my password?” has an answer; both are nullable, and an address that cannot be honestly determined is left empty rather than guessed at.
email_verification_tokens- The same shape: a hash of the link, its expiry, when it was used — plus the address the link was sent to, captured at the time so that changing your address afterwards cannot cause an old link to verify the new one.
auth_attempts- One row per sign-in or reset attempt, holding what was being limited — a lowercased email address, or an IP address — the flow, and whether it succeeded. This is the rate limit: both are recorded because limiting by address alone lets one password be sprayed across many accounts, and limiting by IP alone lets a distributed attacker grind one. A successful sign-in clears the history for that email address.
contact_messages- A message sent through the contact form: the address you gave to be replied to, an optional name, the message itself, and the IP address it came from, which is how the form is rate limited. If you were signed in when you sent it, your user and workspace ids are recorded too, so that "my renders keep failing" can be looked into — context, not ownership. This is the only table here that holds data from people who are not customers, because the form does not require an account.
reviews- A review you chose to write about ClipSpell: the star rating, your words if you wrote any, whether you ticked the box allowing ClipSpell to show your words and name on its home page and when you ticked it, and — only if you did — the name and the channel handle you chose to show. Also where it stands: waiting to be read, published, not published with the reason (spam, abuse or personal data — never the rating), and when and by which operator that was decided. Whether you have made a video is not stored here; it is checked against your renders each time a review is shown. Removing it in Settings erases the words, the name and the handle at once and keeps only the rating and the fact that it was removed; closing your account deletes it entirely.
review_prompt_dismissals- That you pressed “Not now” on the review prompt, and when, so that you are not asked again. Nothing else. Deleted when you close your account.
audit_logs- What an operator did across the installation, two things you do on the Billing page and the emailed acknowledgement of one of them, and each refund or dispute of a payment: the action, who did it, the workspace and object it was done to, and a metadata document. The columns include an IP address. Written by operator actions — an administrative credit adjustment, and a decision on a review (publishing it, not publishing it with the reason, or returning it to the queue), which records the review’s rating beside the decision — and by you in two places: when you tick the box asking for a plan to start as soon as you pay, which records the words you ticked and when; and when you withdraw from a purchase, which records the name and email address you gave for the acknowledgement, the plan, its Stripe subscription id, when it was bought and when you withdrew. Those two are the record of what you agreed to and what you asked for, which the refund depends on. And by ClipSpell itself: when it emails you the acknowledgement of a withdrawal, that it did and the address it went to; and when Stripe reports that a payment was refunded or disputed, so the operator sees it, the Stripe event, charge and invoice ids, the amount and currency, how many credits were taken back and how many had already been spent, and for a dispute the date its answer is due.
brand_kits- A logo, a watermark, colours and voice rules that a project can point at, materialised into a render by the pipeline. You create these in Settings → Brand, and the logo and watermark are files you upload; the image bytes live in object storage and this row points at them.
ad_projects- Ad Studio, which is not open to customers: only the installation’s operator can use it. A campaign set up there: its name, whether it is an ad, a promotion or a demo, what is advertised and its name, whether it is archived, who created it, and when it was created, changed or deleted.
ad_assets- Ad Studio (operator only): one row per file kept there — a picture uploaded as a source, and website captures, generated backgrounds and finished ads. The uploaded file’s name, the storage key, a SHA-256 of the exact bytes, the type decided from the content, size, dimensions, duration and frame rate, which file it was made from, what it is for and whether it was chosen, and where it came from: for an upload, who confirmed the right to use it and when; for a fetched file, the address it came from and when. The file itself is in private storage and is served only through a route that checks access on every request.
ad_briefs- Ad Studio (operator only): each saved version of a project’s brief, never edited afterwards — what was entered, the structured brief made from it, whether the built-in template or a language model wrote it and which model, the template’s version, a hash of its content, who saved it and when.
ad_jobs- Ad Studio (operator only): each import, website capture or ad-making job — its project, the brief version and its hash, the settings asked for, the files it reads, the request key and a hash of the request (so sending it twice is one job), its state and stage, attempts, lease and heartbeat times, the error shown (a code, a sentence, whether it is retried and a correlation id), its result, who started it, and timestamps.
ad_job_events- Ad Studio (operator only): a job’s history — each change of state or stage, each attempt and error, and when.
ad_job_steps- Ad Studio (operator only): which steps of a job are done and what each produced, so a retried job does not repeat them.
ad_provider_operations- Ad Studio (operator only): each request a job makes to an image generator, under a stable key — the provider, whether it is local or paid, the provider’s own id for the request, its state, the file it produced and any error — so an unclear outcome is checked rather than sent twice.
ad_usage- Ad Studio (operator only): what each job counted against the daily limits — the person and the job, the unit and quantity, whether it is reserved, used or given back, and an estimated and actual cost where one is known.
ad_capability_snapshots- Ad Studio (operator only): what the background worker last found it can do on this server — which kinds of job it runs and whether its tools, fonts, capture browser and image generator passed their checks — with the worker’s id and when it checked. No personal data.
ad_self_settings- Advertise yourself, part of Ad Studio (operator only): one row per workspace — the Ad Studio project its pictures, briefs and jobs belong to, the language its ads are written in, who set it up and when.
ad_platform_profiles- Advertise yourself (operator only): each version of this site’s own profile, which is read instead of asking the operator to describe the site — its version number, whether the built-in facts or a later reading made it, a hash of its facts, what changed from the version before, how many facts it holds and how many may appear in an ad, who made it and when. No personal data.
ad_platform_facts- Advertise yourself (operator only): the facts of one profile version — the site’s own sentences, claims, plans and prices, colours and fonts — each with whether it may appear in an ad, the words and figures an ad may use with it, the public pages that show it, and where it was read (a file of the code, a table, or the name of a setting, never its value). No personal data.
ad_platform_profile_assets- Advertise yourself (operator only): which kept files are the site’s logo and pictures in a profile version — the file, what it is for, its handle, the page and screen size it shows and the job that made it; for a screenshot or recording the operator added, who confirmed that it shows no personal data, no customer’s work and no other account’s items, and when.
ad_self_runs- Advertise yourself (operator only): each press of its buttons — the settings chosen and what they became, the profile version used, whether a language model or the built-in template wrote the concepts and which model, notices and errors as codes, counts and times of the work, the request key and a hash of the request (so pressing twice is one run), who pressed it and when.
ad_self_concepts- Advertise yourself (operator only): every ad concept proposed, kept so later ones differ — what it advertises, its angle, format and style, title, audience, opening line, message and call to action, the post text and hashtags made from them, the reason given, its scenes, how it was checked against the profile, and whether it was chosen, kept as a spare or dropped.
ad_self_items- Advertise yourself (operator only): the steps of a run, done one at a time — a page capture, a picture import, the concept step or one ad — with their order and state, the concept and brief an ad is made from, the job that does the step and any error as a code.
project_sources- For a factual video — the documentary, explainer, fast facts, list, mystery and news formats, a motivational, story or custom video whose topic names someone or somewhere in two words or more, or a year, and a product video whose topic is a dated history: a past year, a person, place, company or product named in two words or more, and how something was invented, founded, created or introduced, or began, or its history or origin, in no seller’s own words such as “our”, “your”, “new” or “introducing” — the text of the Wikipedia article its script was written against, the address of the exact revision that was read, and when it was fetched, so that a job which is retried reads the same article. Text published by Wikipedia, not anything about you, and erased with the rest of the workspace’s projects when its data is erased.
Files
Generated audio, images, previews and finished MP4s are written to object storage under keys namespaced ws/<workspaceId>/<lifecycle>/…. They are never served as public static assets. Every read goes through a signed, expiring URL — fifteen minutes by default — or, for the captions downloaded as an SRT file, through a route that checks your sign-in and workspace first; and the key’s workspace prefix is re-checked against the caller’s workspace on every issue and every read.
Server logs
Logs are structured JSON on stdout and stderr, with a job id and workspace id bound to every line so a support request can be traced. Redaction is applied centrally in the logger rather than at each call site: fields whose names look sensitive are replaced, and so are values that look like a secret regardless of the field name — API-key prefixes and JWT-shaped strings among them.
They stay on this machine. Each service’s output is appended to its own file there; when the platform starts, a file past 2 MB is moved aside and a new one begun, the five earlier files are kept and the oldest is deleted. No clock removes a line, so how far back the logs reach depends on how much the services write and how often they restart, not on a fixed period. No log aggregator receives them, and they are never sent to a third party. A Sentry DSN is accepted by the configuration and nothing reports to it.
4. Declared in the database, not written today
The schema declares tables for features that are not built. They exist so that switching a feature on is not a migration. No code reads or writes any of these, so they hold nothing. They are listed because a data inventory that quietly omits a table is the specific way this page could mislead — and because if one of them starts being written, this page has to change in the same commit.
accounts— linked third-party sign-in providers — the web app implements only email and passwordidempotency_keys— no route stores an idempotency record yet
Written, because billing is configured on this installation
Billing is connected here, so these three are in use. They hold Stripe’s own identifiers and the state of a subscription — never a card number, which has no column to go in.
plans— the plans offered for sale, with their prices and limits. No personal data of any kind — it is a catalogue, identical for every visitorsubscriptions— one row per workspace subscription: Stripe’s customer id, its subscription id, the status and the period dates. No name, no address and no card number — none of those has a columnwebhook_events— the record of each Stripe event received, so a redelivery is recognised rather than applied twice. It stores the event as Stripe sent it, which is how a billing dispute can be answered from evidence rather than from memory
One column on a table that is used
sessions.user_agent is declared and never written, so your browser string is not recorded against a session. sessions.ip_address beside it is written, and section 3 says so — an earlier version of this page said both were unused and that we had no record of the addresses you signed in from, which was not true.
6. What is sent to an AI model
Two hosted AI providers have an adapter in this codebase, and each sends something only when the operator has configured a key for it.
Anthropic’s Claude models write scripts when the operator has configured an Anthropic key. Without one, a local language model running on the server writes them if the operator has enabled it, and a deterministic template writes them if not. Only the first of those three sends your script request to a third party; while research is switched on, the topic of a video that is looked up (a factual one; a motivational, story or custom one whose topic names someone or somewhere in two words or more, or a year; or a product one whose topic is a dated history: a past year, a person, place, company or product named in two words or more, and how something was invented, founded, created or introduced, or began, or its history or origin, in no seller’s own words such as “our”, “your”, “new” or “introducing”, as section 7 describes) also goes to Wikipedia when either of the first two writes it, and nothing is looked up for the template. The product records which one wrote your script rather than presenting any of them as another.
ElevenLabs synthesises narration. With no ElevenLabs key configured it is never called and the voice is local. With one configured it outranks the local voices, so your narration text is sent to ElevenLabs and the audio comes back from them. Neither this installation nor any default sets that key.
What a script request contains
The topic you typed — or the script you pasted, if you supplied one — plus the target platform, duration, language, tone and template, and, where you set them, the audience, the objective, brand voice instructions, banned terms, and the titles and angles of the earlier videos in the same series.
For a video that is looked up (a factual one; a motivational, story or custom one whose topic names someone or somewhere in two words or more, or a year; or a product one whose topic is a dated history: a past year, a person, place, company or product named in two words or more, and how something was invented, founded, created or introduced, or began, or its history or origin, in no seller’s own words such as “our”, “your”, “new” or “introducing”) it also carries the text of the Wikipedia article the script is written against, marked for the model as material to draw on and never as instructions. That article is found by sending your topic to Wikipedia, as section 7 describes.
It does not contain your email address, your name, your account, user or workspace identifiers, your credits or billing state, any credential, any whole database row, or anything belonging to another customer. That is an architectural rule in this repository, not an aspiration.
What stays on the machine
Unless an Anthropic key is set, the script is written on this machine too — by a local language model if the operator has enabled one, otherwise by the deterministic template. Unless an ElevenLabs key is set, narration is synthesised locally, either by the Kokoro neural voice running on the CPU or by the host’s own system voice. Each recorded line is listened back to by a speech recogniser, whisper.cpp, running on this machine, to check that the voice said its figures and names. Illustration is either a local ComfyUI server or backgrounds generated by ffmpeg. Each drawn picture, with the narration and the shot description of its scene, is checked by a picture-review model running on this machine. The render itself is ffmpeg. None of these send your text, audio or video anywhere — but while research is switched on, the topic of a video that is looked up (a factual one; a motivational, story or custom one whose topic names someone or somewhere in two words or more, or a year; or a product one whose topic is a dated history: a past year, a person, place, company or product named in two words or more, and how something was invented, founded, created or introduced, or began, or its history or origin, in no seller’s own words such as “our”, “your”, “new” or “introducing”) is sent to Wikipedia to find the article its script is written against, whichever model writes it, as section 7 describes.
Keys the configuration accepts but no code uses
The environment schema accepts keys for OpenAI, Replicate, fal, Pexels, Pixabay, Tavily and Brave. No adapter exists for any of them, so setting one of those keys sends nothing to anybody. They are named here only so that nobody reading the configuration file assumes a processor relationship that does not exist.
This paragraph listed ElevenLabs among them until 19 September 2026, and that was wrong: the adapter exists, it is registered whenever a key is present, and it outranks the local voice. It is described above and listed in section 7 instead.
Training
We do not train models on your content, and we do not have a pipeline that could: there is no export of customer data to any training system in this codebase. The specific undertaking about data obtained through Google APIs is in section 8.
7. Who else processes your data
- The database. A PostgreSQL server on the operator’s own machine, in Bulgaria. It is not a managed service and no third party holds a copy. A production process refuses to start against an embedded development database; this installation runs as a development process, which is why that guard does not apply to it.
- Object storage. None. Rendered video, audio and images are written to a directory on the operator’s own machine, the same one the database is on. No bucket exists and no storage provider receives anything.
- Application hosting. The operator’s own machine, reachable at this address through a Cloudflare tunnel. Cloudflare therefore carries the traffic between you and that machine and can see the connection, as any network provider between two points can; it is not sent your account data to hold. Nothing is deployed to a hosting provider.
- Anthropic. Script generation only, on the terms in section 6, and only when a key is configured.
- ElevenLabs. Narration synthesis only, and only when an ElevenLabs key is configured — which on this installation it is not. With one set, the text to be spoken is sent to them and the audio is returned; nothing else about you goes with it. Section 6.
- Wikipedia (the Wikimedia Foundation). Factual videos only — the documentary, explainer, fast facts, list, mystery and news formats, and a motivational, story or custom video whose topic names someone or somewhere in two words or more (“Thomas Edison”, “West Orange”) or a year, and a product video whose topic is a dated history: a past year, a person, place, company or product named in two words or more, and how something was invented, founded, created or introduced, or began, or its history or origin, in no seller’s own words such as “our”, “your”, “new” or “introducing”, as the QR code’s is (“the factory problem Denso Wave invented it to solve in 1994”) — and only when a language model writes the script: the template writer reads no article, so nothing is looked up for it. The topic you typed, up to its first colon or question mark, is sent to Wikipedia’s public search to find the article the script is written against and checked against, and that article’s text is fetched back; a topic with nothing before the mark is not sent at all. When the part before the mark names nobody and the part after it opens with a name (“Real comebacks from history: Abraham Lincoln, who …”), that name alone is sent first. Nothing about your account is sent with it: no name, no email address, no account or workspace identifier. Any other product video, a story whose topic says it is made up (“A ghost story: …”), any other motivational, story or custom video, and scripts you paste, are never looked up.
- The platform you connect. When you connect a social account and publish, the rendered video, the title, the caption and the hashtags go to that platform. Sections 8, 9 and 10.
Three services worth stating precisely, because the honest answer differs for each.
- Stripe. Paid plans are sold through Stripe, so if you buy one, your card details go to Stripe and never to us: checkout and the billing portal are Stripe-hosted pages, and what comes back is a customer id, a subscription id, a status and period dates. There is no card number anywhere in the schema, and there is no field it could go in. Stripe is a processor in its own right and its own notice governs what it does with what you type on its pages.
- Email. The service does send email — a password-reset link, a password-changed notice, address verification, an invitation to a workspace, and the acknowledgement of a withdrawal from a purchase — through whichever provider is configured, which on a default installation is a console driver that prints the message to the server’s log and sends nothing. Where a real provider is configured, the recipient address and the message reach it. On this installation that provider is Yahoo: messages are sent from the operator’s own mailbox there, over an encrypted connection, so Yahoo receives the recipient address and the message as it would for any mail sent from that mailbox, and handles them under its own privacy terms. The reset page says which of the two is happening rather than leaving you waiting.
- Sentry. A DSN is accepted by the configuration and nothing reports to it. No error data leaves the machine.
We do not sell personal data, we do not share it for advertising, and we do not combine it with data from other sources.
8. Google and YouTube API Services
ClipSpell uses YouTube API Services. By connecting a YouTube channel you are also agreeing to the YouTube Terms of Service, and Google’s own handling of your data is described in the Google Privacy Policy.
YouTube API Data: what ClipSpell accesses, collects, stores and uses
This applies only if you connect a YouTube channel. You sign in to ClipSpell with an email address and a password, never with your Google account. ClipSpell asks Google for no sign-in permission, only the two YouTube permissions listed under “What we ask Google for” below, so it never receives your Google account’s email address. The only name it keeps is your channel’s title, which may be your own name; the channel’s picture arrives in the same reply as the title and is not kept. This is everything ClipSpell obtains from Google and YouTube, where it comes from, what it is used for and where it is kept:
- Google’s access token and refresh token for your channel, when the access token expires, and the permissions you granted. From Google’s authorisation service when you connect, and a new access token each time ClipSpell renews it. Used to make the calls in this list on your behalf, and to refuse a connection that was not given upload permission. Kept in ClipSpell’s database, the tokens sealed with AES-256-GCM, and shown to nobody.
- Your channel’s id and title. From YouTube’s channels list (
part=snippet, mine=true) when you connect and each time ClipSpell re-confirms the connection. Google’s reply also carries other public details of the channel, which ClipSpell does not keep or use. Used to show the members of your workspace which channel is connected, and to check that the permission still reaches that same channel. Kept in ClipSpell’s database. - For each video ClipSpell uploads for you, its YouTube video id and watch link. From YouTube’s reply to the upload (
videos.insert). While the upload is under way ClipSpell also keeps the address of the upload session YouTube opened for it, so that an interrupted upload is resumed instead of sent twice; the video id replaces it once the upload is done. If the upload fails, the address stays so that a retry can resume it. Used to link to the video from ClipSpell and to read its status and counts. Kept in ClipSpell’s database, beside ClipSpell’s own record of the post. - That video’s upload, processing and privacy status. From YouTube’s reply to the upload and from YouTube’s videos list (
part=status,processingDetails). Used to tell you whether the video was published, is still processing or failed. Not kept: ClipSpell turns the answer into its own status for the post, in its own words, and keeps neither YouTube’s answer nor any reason YouTube gives for a rejection or a failure — YouTube Studio shows those. If YouTube gives the video a different privacy status from the one you chose, ClipSpell’s server log records the two values, without the video id. - That video’s view, like and comment counts. From YouTube’s videos list (
part=statistics), asked about that video’s own id and no other. Used to show the members of your workspace each video’s counts on the Analytics page, each with the time it was read; YouTube’s figures are never added together with another platform’s. Kept in ClipSpell’s database as dated readings. A count YouTube does not report — likes the creator has hidden, for example — is recorded as not reported, never as zero, and the other statistics in YouTube’s reply are not kept. - Google’s error codes when it refuses one of these requests. From the reply to the refused request, which ClipSpell reads to decide whether to try again: it keeps the request’s HTTP status and the short codes Google gives for the refusal, such as
quotaExceededorinvalid_grant, and none of the text of Google’s reply. Used to find out what went wrong. Kept in the server logs (section 3) and, when it happened while a video was being published, with that publishing task in ClipSpell’s job queue.
ClipSpell does not read your subscriber numbers, your watch history, the text of any comment, YouTube Analytics reports, or any video it did not upload, and it never edits, hides or deletes anything already on your channel. What it obtains is shown only to the members of the workspace that connected the channel. It is not sold, not used for advertising, never put into a request to an AI model, and not transferred to anyone except as needed to perform the upload you asked for, or where the law requires it. How often each part is refreshed, and when it is deleted, is set out below under “How often it is refreshed, and when it is deleted”.
What we ask Google for
Two OAuth scopes, and nothing broader:
https://www.googleapis.com/auth/youtube.upload— to upload one video that you asked us to upload.https://www.googleapis.com/auth/youtube.readonly— to identify which channel you connected and re-confirm it every few days, to read the processing state of a video we uploaded, and to read the view, like and comment counts YouTube reports for videos ClipSpell uploaded, which the Analytics page shows.
We deliberately do not request the full youtube management scope. ClipSpell only uploads the videos you publish and makes the reads this section lists; it never edits, hides or deletes anything already on your channel. The authorisation request uses access_type=offline so that a refresh token is issued — without it a connection would stop working an hour later with no way to renew it.
Google’s consent screen lets you untick individual permissions. If upload permission is not granted, we refuse the connection at that moment and store nothing, rather than discovering it when you try to publish.
What we read, and what we store
Immediately after the grant we call the channels endpoint for your own channel and keep two things: the channel id and the channel title, so the interface can show you which channel is connected. For each video ClipSpell uploads we keep its video id and watch link, and we read its processing state and its view, like and comment counts, which the Analytics page shows as dated readings. We do not read your subscriber numbers, your watch history, the text of any comment, YouTube Analytics reports, or any video ClipSpell did not upload.
How long we keep it
ClipSpell renews its access and re-reads the channel id and title every few days while connected; that renewal is how it confirms you have not withdrawn permission. A video’s id, link and figures are re-read at least weekly while ClipSpell can still read the video. Anything from YouTube that ClipSpell has not been able to refresh for 28 days is deleted from ClipSpell’s database — the channel id and title, video ids and links, and every reading; “How often it is refreshed, and when it is deleted” below says what this means for the backups and the server logs. Each dated reading of a video’s figures is also deleted once it is 28 days old, because a reading is never refreshed, only followed by a newer one; the Analytics page therefore shows a video’s figures for those last 28 days at most. Disconnecting or closing your account deletes all of it at once. What stays is ClipSpell’s own record that a video was published to YouTube, when, and with the caption and hashtags you wrote.
How often it is refreshed, and when it is deleted
ClipSpell’s background worker keeps this up to date and deletes it:
- Channel id and title: re-read every few days while the channel is connected, renewing the access token each time; that renewal is how a permission withdrawn at Google is noticed. Deleted at once when you disconnect the channel or close your account, within about a day of ClipSpell noticing that the permission was withdrawn or stopped working, and in any case once 28 days have passed without a confirmation.
- Video id and watch link: refreshed each time the video’s counts are read successfully. Deleted at once when you disconnect or close your account, and otherwise once they have gone 28 days without a successful read — for example because the video was deleted from YouTube. The upload session’s address is replaced by the video id as soon as the upload is done. If the upload fails, the address is kept so that a retry can resume it, and is deleted at the latest once 28 days have passed since the post last changed, or at once when you disconnect or close your account.
- Upload, processing and privacy status: not kept, so there is nothing to delete. Read while ClipSpell waits for YouTube to finish processing an upload.
- View, like and comment counts: read again on a schedule that slows as the video ages — every 30 minutes until it is 6 hours old, every 2 hours until it is a day old, every 12 hours until it is a week old, every 3 days until it is 30 days old, and every 7 days after that — for as long as ClipSpell can still read the video. A reading is never changed, only followed by a newer one, so each reading is deleted once it is 28 days old, and all of them at once when you disconnect or close your account.
- Tokens: the access token is renewed when it is about to expire and at each re-confirmation. Both tokens, their expiry and the permissions you granted are deleted at once when you disconnect or close your account, or when the channel’s data is deleted as above, and disconnecting and closing your account also ask Google to revoke them.
- Error codes of a refused request: deleted with the publishing task that holds them 28 days after that task finished. The server logs, which hold the same codes, are rotated by size rather than by age (section 3), so a copy there can be kept longer; log lines written before 8 October 2026 can also hold Google’s own explanation of a refused request.
- Backups: the database, with the data above, is backed up every night on the operator’s machine, and each nightly backup is deleted once seven newer ones have been made, normally seven nights later. A copy of the database is also taken before each change to its structure, and each of those is deleted once five newer ones have been taken, however long that takes. A deletion above reaches a backup only when that backup is itself deleted.
The worker looks for deletions that are due every hour, for channels due a re-confirmation every six hours, and for counts due a read every 10 minutes.
What we write
One resumable upload per publication you asked for. It carries the video file and sets these fields on the new video, and no others:
snippet.title— the titlesnippet.description— the description: your caption, with your hashtags after itsnippet.tags— the same hashtags, without their # signs, as the video’s tagssnippet.categoryId— a YouTube category taken from the video’s template: Education, Entertainment, News & Politics, Science & Technology, or People & Blogssnippet.defaultLanguage— the language of the narration, as the video’s language, when ClipSpell knows itsnippet.defaultAudioLanguage— the same language, as the language of the video’s audio, when ClipSpell knows itstatus.privacyStatus— the visibility you chose for the upload: public, unlisted or privatestatus.selfDeclaredMadeForKids— the made-for-kids declaration you setstatus.containsSyntheticMedia— YouTube’s flag that the video contains synthetic content, set on every upload because every ClipSpell video has a synthetic voice and generated visuals (terms section 10)status.embeddable— that the video may be embedded on other websites, set on every upload
Nothing else is written to your channel.
You choose each upload’s visibility — public, unlisted or private — on the publish panel, and once for each channel a series posts to; ClipSpell sends YouTube that choice as it is. A series channel with no visibility chosen yet uploads as private until one is. The Google Cloud project behind this deployment has passed the YouTube API compliance audit, so YouTube keeps the visibility chosen for each upload.
Limited Use
ClipSpell’s use and transfer to any other app of information received from Google APIs will adhere to the Google API Services User Data Policy, including the Limited Use requirements.
In particular: data obtained through Google APIs is not used to develop, improve or train generalised or non-personalised AI or ML models. It is used only to provide and improve the feature you connected it for — uploading your video to your channel, reporting its status back to you, and showing you the view, like and comment counts YouTube reports for it. It is not sold, not used for advertising, and not transferred to anyone except as needed to perform that upload.
How your Google user data is protected
Google user data here means what ClipSpell obtains through Google APIs, which “YouTube API Data” at the top of this section lists in full: the access and refresh tokens Google issues, your channel id and title, the id and link of each video ClipSpell uploads, and the view, like and comment counts YouTube reports for those videos. Everything in section 14, Security and data protection, applies to it, and in particular:
- The tokens are sealed with AES-256-GCM before they are stored, with a key that is not kept in the database, and every call to Google is made over https.
- Inside ClipSpell, the channel and video details and the counts are shown only to the members of the workspace that connected the channel, the tokens to nobody, and the operator area reads none of it.
- It is not sold, not used for advertising, and not transferred to anyone except as needed to perform the upload you asked for, or where the law requires it.
- It is not used to develop, improve or train generalised or non-personalised AI or machine-learning models, and none of it is ever put into a request to a language model: a script request carries only what section 6 lists.
- No person reads it unless you ask us to — about a particular upload in a support request, for example — unless it is necessary for security purposes such as investigating abuse, or unless the law requires it.
- It is deleted as “How long we keep it” above says: at once when you disconnect the channel or close your account, and otherwise within the times that paragraph gives, from the database; the nightly backups of the database kept on the operator’s machine hold it until they are replaced, after seven nights.
How to take our access away
You can revoke ClipSpell’s access to your Google account at any time, without asking us, at https://myaccount.google.com/permissions (also reachable at https://security.google.com/settings/security/permissions). Doing that immediately stops any further upload. ClipSpell notices the next time it renews its access — within about three days — and deletes what it holds from YouTube for that channel within about a day of noticing. If it cannot reach Google to ask, that data is still deleted 28 days after its access last worked. Disconnecting inside ClipSpell also asks Google to revoke the token and deletes that data at once — see section 11.
9. TikTok
The TikTok adapter asks for user.info.basic plus exactly one posting permission, never both: video.upload when the deployment is configured to deliver drafts, or video.publish when it is configured to post directly. Draft delivery is the default, because it puts the video into your TikTok drafts and you tap publish in the app yourself.
From user.info.basic we read your TikTok open id and your display name, and we store both — the open id identifies the account to the API, the display name is what the interface shows you. If the display-name call fails the connection still works; it is a courtesy, not a requirement.
For a direct post, TikTok requires us to query your creator information first, because a private account genuinely cannot receive a public video. That response tells us your TikTok nickname, which privacy levels you may use, whether you have turned comments, duets or stitches off in TikTok, and your maximum post duration. We show it on the screen you post from and use it to decide what to ask for; the response is not stored. What is stored is what you choose on that screen: who can see the video, whether others may comment, duet and stitch, and whether it promotes your own brand or is branded content. A series keeps those answers for each TikTok account it posts to, because nobody is there when it posts.
On YouTube and Instagram, where you answer whether a video promotes your own brand or a brand paid for it, that answer is stored on the publication too, so the notice that it was posted can tell you which of the platform’s own labels to set.
For a direct post, what we send is the rendered video, the caption and the hashtags, with the answers above and TikTok’s AI-generated content label. A draft is the rendered video alone: TikTok’s draft upload carries no caption or hashtags, so you add them in TikTok before posting, and the final wording is yours. The video’s page in ClipSpell shows the caption ready to copy.
A TikTok application that has not passed TikTok’s content-posting audit is confined to posts only the creator can see. This deployment has not passed that audit unless the operator has said so in configuration, so direct public posting is not available and the product says so instead of offering it.
10. Instagram and Meta
The Instagram adapter uses Instagram API with Instagram Login and asks for two scopes: instagram_business_basic, which identifies the account, and instagram_business_content_publish, which is the one that posts. Nothing about messages, comments or insights is requested — those are separate permissions and this product does not need them.
After the grant we read your Instagram user id, your username and your account type, and we store the id and the username. The account type is checked so that a personal account is refused at connect time with an explanation, rather than at the first failed post.
Instagram is the one platform that does not accept an upload: it downloads the file from a URL we give it. Publishing a Reel therefore means handing Instagram a link to your rendered video in our object storage, which Instagram’s servers then fetch. A deployment whose storage cannot issue such a link — local development disk, for instance — is refused by the adapter rather than sent a URL that cannot be reached.
Instagram’s publishing API has no per-post privacy setting; a Reel is as visible as the account is. Meta has not yet granted this application App Review for the publishing permission, so publishing works only for accounts that have a role — admin, developer or tester — on the operator’s own Meta app. Connecting any other account will not be able to post.
11. Connected accounts, tokens and disconnecting
What a connected account record holds
- The platform, and the platform’s own id for the account.
- The account name the platform gave us, for showing you which account it is.
- The scopes the platform actually granted, which is not always what was asked for.
- The access token and, where the platform issues one, the refresh token — both encrypted, see below.
- When each token expires. Refresh tokens are recorded too, because they lapse on a long clock that nobody notices until it has passed.
- A state — connected, expired, revoked, or “needs review” — and a plain-language reason when it is not connected. “Needs review” is about our application, not your account.
- Which member of your workspace connected it, platform specifics such as page ids, when it was last refreshed and last used, and, for YouTube, when it was last confirmed with the platform.
How the tokens are protected
Access and refresh tokens are sealed with AES-256-GCM — authenticated encryption, 96-bit nonce — before they are written to a column. The envelope carries the version of the key that sealed it, so a key can be rotated and old rows re-wrapped lazily instead of forcing every account to re-authorise.
Three things enforce that rather than trusting a code reviewer. The columns are named access_token_envelope and refresh_token_envelope, so a query returning one cannot be mistaken for something usable. A database CHECK constraint rejects any value that does not begin with a key-version marker, which catches a plaintext token being written by mistake at the one place it matters. And the repository that reads connections selects columns explicitly, with the envelope columns left out — so a connection handed to a page or an API response cannot carry a credential by accident. There is exactly one function that returns them, it is named for what it does, and it returns a different type.
A production process refuses to start without an encryption key configured. There is no mode in which these tokens are stored in the clear.
What happens when you disconnect
Disconnecting is deliberately not a row deletion. The record of what was published, where, and on whose authority has to outlive the disconnection, so:
- The stored access token is overwritten with a sealed empty placeholder and the refresh token is set to null. There is no longer a usable credential in the database.
- Both expiry timestamps are cleared.
- The connection is marked
revokedwith the reason recorded and the time it happened, so nothing can be published through it again. - For YouTube and TikTok, we also ask the platform to revoke the token at their end, so the grant is really gone and not merely forgotten by us. If the platform refuses because the token is already invalid, that is the desired end state anyway and the disconnect still succeeds. Instagram is the exception: Meta offers no way for us to hand the grant back, so remove ClipSpell from your Instagram settings as well if you want it gone at their end too.
- For YouTube, what came from YouTube is deleted at once. Disconnecting deletes the channel id and title, each video’s id and link, and every view, like and comment reading. The account then shows as “YouTube channel (details deleted)”.
- The publication history is kept on purpose. The rows recording what you published stay, and the database refuses to delete a connection that publications point at. Those rows contain the platform, the caption and hashtags you used, the timestamps, and — for TikTok and Instagram — the post id and URL. None of it is a credential. If you want that history removed as well, ask us: section 13.
Revoking at the platform’s end — Google’s permissions page, or the equivalent inside TikTok or Instagram — also works and does not require us.
12. How long we keep things
Plainly: very little expires on a timer. No project, render, job or ledger row is deleted by age, and a policy quoting a retention period for them would be describing a control that does not exist. What is swept is narrower and listed below: dead sessions, spent one-time links, notifications older than ninety days, contact messages after 24 months, data from YouTube that ClipSpell can no longer refresh, and each reading of a YouTube video’s figures once it is 28 days old.
What does exist:
- Every storage key encodes a lifecycle class, and the code declares the retention a deployment’s bucket lifecycle policy should apply to each: intermediate render artifacts 2 days, cache 30 days, and no expiry at all for source files, for regenerable derived files, or for your finished exports. Derived files carry no expiry on purpose — a bucket rule cannot tell whether a project still points at one, so removing them has to be driven from the database rather than from the clock. Those numbers are a declaration for the operator to configure at the bucket, not something the application enforces. There is no bucket on this installation — files are on the operator’s own disk — so no lifecycle rule applies and nothing is expired automatically. The classes above describe what a hosted deployment should configure, not what happens here.
- Session rows carry an expiry 30 days out and are not accepted after it. An hourly sweep in the worker then deletes them, with the periods read out of the code rather than described loosely: an expired or revoked session is removed 7 days after it died — the grace exists because a row revoked last night is the evidence behind “you were signed out on another device” — and rate-limit rows in
auth_attemptsare removed after 24 hours, which is longer than the longest limiting window and no longer. Spent and expired password-reset and email-confirmation links go on the same hourly pass. - In-product notifications — a video finishing, a publication failing — are removed 90 days after they were written, on that same hourly pass. Long enough that “what happened to that video last quarter” still has an answer, and one of the few things in this section that expires on a timer.
- Messages sent through the contact form are kept until somebody has dealt with them and then kept after that, because “what did this person ask us three weeks ago” is a question with a real answer in a table and a shrug in a mailbox. Removed automatically after 24 months. The worker’s hourly housekeeping sweep deletes them, on the same pass that prunes expired sessions and old notifications. If you sent one and want it gone sooner, ask at the address in section 18 and say which; that one is done by hand.
- Data from YouTube is deleted when you disconnect the channel or close your account, within a few days of your withdrawing ClipSpell’s access at Google, and otherwise 28 days after ClipSpell last managed to refresh it — and each reading of a video’s view, like and comment counts 28 days after it was taken, newer readings staying — by the worker’s hourly sweep (section 8). This is a YouTube API Services requirement.
- Closing your account is a button now, in Settings. It signs out every device immediately, releases your email address so you could sign up again, clears your stored password, and closes any workspace where you were the only member. A workspace you share with other people keeps running without you — one person leaving must not destroy a colleague’s work. Every YouTube channel you connected is disconnected at once — in a workspace you share too — Google is asked to revoke ClipSpell’s access, and what ClipSpell obtained from YouTube for it is deleted. A review you wrote is deleted at once as well, so it leaves the home page that moment. This marks the account closed; it does not yet erase the stored bytes, which is the next item.
- A review you wrote is kept until you remove it in Settings, which erases its words, your name and your handle at once, or until you close your account, which deletes it. It is shown on the home page only while you allow it and only once a person has approved it.
- Erasure of the underlying rows and files is still a request to us, by email. Closing an account stops all access to it; a separate, deliberate step removes the projects, renders and media behind it, and nothing in this codebase does that on a timer. Saying otherwise would describe a control that does not exist.
- The credit ledger is append-only by design. If you ask us to delete your account, the ledger rows needed to answer a future billing dispute may be retained where the law allows or requires it, and we do keep them. It is the only record that can answer “what was I charged for” two years later, and it holds nothing about you beyond a workspace id, an amount, a reason and a timestamp — no name, no email, no address. Everything that does identify you goes with the account.
13. Your rights
Because this service is operated from the EU, the GDPR gives you all of the following, whoever and wherever you are — they are not conditional on where you live:
- Access (Article 15). A copy of your personal data and the information on this page, confirmed against your actual rows rather than in general terms.
- Rectification (Article 16). Correction of anything inaccurate. Your name and email you can already change yourself, in settings.
- Erasure (Article 17), with one stated exception. Deletion of your account and its content. The exception is the publication record — what went out, to which platform, when — which section 12 explains is deliberately kept; say in your request if you want it gone as well and it goes.
- Withdrawing consent (Article 7(3)). The one thing here that rests on consent is showing a review you wrote. “Remove my review” in Settings withdraws it: the review leaves the home page at once, and its words and your name are erased.
- Restriction (Article 18) and objection (Article 21). You can object to the security logging described in section 3 and to our keeping a review you sent us, the two things here resting on legitimate interests. For the review, removing it in Settings is the same thing and takes effect at once.
- Portability (Article 20). Your data in a machine-readable form. In practice that is a JSON export produced by hand, because nothing in the product does it yet.
- Complaint to a regulator (Article 77). To Bulgaria’s Commission for Personal Data Protection, whose address is in section 1, or to the authority where you live. You do not have to raise it with us first.
How to exercise them: erasure yourself, the rest by email. Closing your account is a button in Settings, and it takes effect immediately — your sessions are revoked and any workspace where you were the only member is closed with it. A workspace with other people in it keeps running, because deleting their work when one person leaves would be the worse outcome; you are told which happened to each. Every YouTube channel you connected is disconnected at once — in a workspace you share too — Google is asked to revoke ClipSpell’s access, and what ClipSpell obtained from YouTube for it is deleted.
There is no self-service export, and for everything else — access, rectifying something, objecting, or a deletion that goes further than closing the account — write to clipspell@yahoo.com. Article 12(3) sets the deadline at one month, extendable by two further months for a complex request and only if we tell you why within the first month — that is the commitment, and it is the law’s rather than a number chosen here.
If you arrived from Instagram or Facebook and want the data tied to that connection removed, their apps send the request to our data deletion page, which reports what was removed and gives you a reference to quote.
One related gap worth knowing about: disconnecting a social account intentionally keeps the publication history, so a deletion request should say whether you want that removed too.
14. Security and data protection
How your data is protected
What the software, and the way it is served, do today. Nothing beyond it is claimed: there is no certification and no independent audit, and ClipSpell does not encrypt the database or the disk as a whole.
- In transit. On the internet, ClipSpell is served over https, through Cloudflare and with Cloudflare’s certificate. Between Cloudflare and the operator’s machine the traffic travels inside Cloudflare’s encrypted tunnel, and the last step, from the tunnel to the application, does not leave that machine. Your sign-in cookie is marked Secure, so your browser does not send it over plain http. Every request ClipSpell’s servers make to a service outside the operator’s machine — Google and YouTube, TikTok, Instagram, Stripe, the email provider, and the hosted AI and research services in sections 6 and 7 when they are used — goes to that service’s own https address, and email is sent over TLS with the mail server’s certificate checked: there is no unencrypted fallback. The language model and the image generator and the picture reviewer section 6 describes as running on the operator’s machine are reached on that machine itself, so what is sent to them does not leave it.
- At rest. The access and refresh tokens a platform issues to ClipSpell are sealed with AES-256-GCM before they are written to the database, and section 11 says how that is enforced. The key that seals them is held in the server’s configuration, not in the database: it is never written to a table, to a database backup or to a log. Passwords are never stored — only a scrypt hash of each. The token in your sign-in cookie, and the links that reset a password, confirm an email address or accept an invitation, are stored only as SHA-256 hashes, so a copy of the database holds none of them in a usable form. The rest of the database and the stored files are not encrypted by ClipSpell itself; what protects them is who can reach them, next.
- Who can reach it. The database and the stored files are on the operator’s own machine, and no hosting company holds either (section 7). The database accepts connections only from that machine itself. Stored files are never served directly. A file of yours is reached from outside only through a link the application signs for that file and its workspace, which expires within hours, or through a download that checks your sign-in and workspace first — the captions as an SRT file. The product’s own example pictures are public. Inside the product the workspace is the boundary: every customer-owned row carries a workspace id, every repository takes it as a required argument, and someone who is not a member of a workspace is told it does not exist. Apart from the reviews customers have chosen to have shown on the home page, the one screen that shows customers’ data across workspaces is the operator area, open only to an account holding the platform-administrator role. No page or request can grant that role — it is given from the server itself — and the operator area reads no connected account, no publication and nothing from YouTube. The background jobs that keep platform data up to date, or delete it on time, run across workspaces without showing it to anyone.
- As little as the features need. From YouTube, two permissions:
youtube.upload, to upload the videos you choose to publish, andyoutube.readonly, to identify your channel and read the status and counts of the videos ClipSpell uploaded — never the full management scope (section 8). TikTok and Instagram are asked only for the permissions sections 9 and 10 list. How long each thing is kept, and what deletes it, is section 8 for YouTube and section 12 for everything else; disconnecting a channel or closing your account deletes what came from YouTube at once. - Logs. Redaction is applied centrally in the logger, so no single line can forget it: a field whose name says it holds a password, secret, token, API key, cookie, session or card number is replaced, and so is any value shaped like an API key or a signed token, whatever field it arrives in. When TikTok or Instagram refuses a request, its reply is kept in the error that is logged, with the credentials of that request removed and cut to 500 characters, so the failure can be looked into; when Google refuses one, only the request’s status and Google’s error codes are kept (section 8). Where the logs are kept is section 3.
Other safeguards in place
- Every user-supplied URL passes an SSRF guard that resolves DNS and checks the resolved addresses, re-checking each redirect hop, with hard time and size limits.
- Uploads are identified by content rather than by the declared MIME type or extension, and stored filenames are never the name you supplied.
- A production process refuses to boot against development infrastructure: embedded database, local disk, plaintext or loopback origin, missing secrets.
- Sign-in and password reset are throttled, by email address and by IP address separately — this is the
auth_attemptstable in section 3. Limiting by address alone lets one password be sprayed across many accounts; limiting by IP alone lets a distributed attacker grind one; so both are counted. - A Content-Security-Policy is served on every response, alongside the other security headers. It allows scripts, connections and form submissions only from this origin, forbids plugins and forbids the site being framed.
What is missing
Stated because a security section that overstates is worthless:
- No multi-factor authentication.
- Dependency vulnerability scanning runs on every push and every pull request, but as an advisory job: it reports what it finds and does not stop a release. So it is weaker than a gate, and treating it as one would be the overstatement this list exists to avoid.
- No independent penetration test. The adversarial tests that exist were written by the same author as the code.
- A known time-of-check/time-of-use window remains in the SSRF guard between our DNS lookup and the socket’s own resolution.
- On a personal-data breach we notify the Bulgarian Commission for Personal Data Protection within 72 hours of becoming aware of it, as the GDPR requires, and tell affected people directly where the breach is likely to put their rights at high risk. What that looks like in practice on a one-person operation is honest to state: there is no on-call rota and no automated detection, so becoming aware of it depends on the operator noticing or being told.
15. Children
ClipSpell is a tool for people publishing video commercially or semi-commercially. It is not directed at children and we do not knowingly collect data from anyone under 18. If you believe a child has an account, write to clipspell@yahoo.com and we will remove it.
The “made for kids” value stored on a publication is a declaration several platforms require about the video, which they refuse the upload without. It is not a statement about the age of the person using ClipSpell and we do not use it to profile anyone.
16. International transfers
Your account, your projects and your finished videos are held on the operator’s own machine, in Bulgaria, and are not copied to any hosting provider. Script generation runs on that same machine, on a model installed locally — nothing you type is sent to an AI company. The tunnel that makes the site reachable is Cloudflare’s, so traffic passes through their network in transit.
What does leave it, and where it goes:
- Stripe, if you buy a plan. When you start a checkout, ClipSpell sends Stripe your workspace’s id and, the first time, your email address, which create the customer your payments are recorded against; what you type on Stripe’s own checkout page then goes to Stripe as well. Stripe is established in the United States, is a processor in its own right and publishes the transfer mechanism it relies on.
- Wikipedia, run by the Wikimedia Foundation in the United States: the topic of a video that is looked up (a factual one; a motivational, story or custom one whose topic names someone or somewhere in two words or more, or a year; or a product one whose topic is a dated history: a past year, a person, place, company or product named in two words or more, and how something was invented, founded, created or introduced, or began, or its history or origin, in no seller’s own words such as “our”, “your”, “new” or “introducing”) whose script a language model writes, as section 7 describes, with nothing about your account.
- Yahoo, which carries the mail this service sends: the recipient address and the message, as section 7 describes.
- The platform you publish to, if you connect a social account and publish: the finished video and its caption, in whichever country that platform operates.
For Stripe we rely on Stripe’s own data processing agreement, which incorporates the European Commission’s standard contractual clauses; Stripe publishes it and it governs what Stripe does with what you type on its pages. For a social platform the transfer happens because you chose to publish to it, and that platform’s own terms and privacy notice govern what it does with the video afterwards — we are not a party to that and cannot undertake it on their behalf.
17. Changes to this policy
This page is versioned with the code it describes. When a change to ClipSpell changes what is collected or where it goes, this page is meant to change in the same commit — the terms and the internal note at docs/LEGAL.md record which statements here are commitments made to a platform’s review, so that breaking one is visible.
Material changes are announced by a notice on this site, at least 30 days before they take effect, and the date at the top of this page moves with them. Not by email: the only mail ClipSpell sends goes to one person about something they or a colleague just did — a password reset or change, an address to confirm, an invitation, a withdrawal — and nothing in it writes to every customer, so a notice here is where to look.
18. Contact
Adrian Borislavov Tolev, ul. Vetrusha 5, Pavlovo, Sofia, Bulgaria, clipspell@yahoo.com.