> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.scripe.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Synced knowledge sources

> How GitHub repositories, Google Drive files, Granola meeting notes and a profile's own post history stay in sync with your knowledge base.

Scripe can keep part of your knowledge base in sync with a **GitHub
repository**, with **files you pick in Google Drive**, or with the
**meeting notes in a Granola workspace**. Synced documents are
ordinary knowledge documents once they arrive: every generator, the chat and
the public API read them exactly as they read anything you upload.

A fourth synced folder needs no connection at all: a profile's own
**Post history** — its published LinkedIn posts — appears as a synced folder
in the knowledge base in the new Scripe experience, and posts can be removed
from it one by one. It is described at the end of this page, because it differs
from the three connectors in most of what follows.

These connections are made in the dashboard, not through the API. This page
describes what they do, what they refuse, and what you will see when
something goes wrong — the behaviour the API surfaces reflect.

Throughout this page a **source** is one such connection. That is a
different thing from a [Source](./sources.md) — the long-form raw input
(a recording or a transcript) the content pipeline extracts topics from.

## Where to find it

Open your knowledge base and choose **Add knowledge** → **GitHub repository**,
**Google Drive** or **Granola**. Connecting needs the workspace admin role and
a plan that includes synced knowledge sources; the connect screen says so
rather than failing after you have granted access. GitHub and Google Drive
connect through the provider's own consent screen; Granola connects by
pasting an API key you create in Granola (a **workspace** key, from
Settings → Connectors → Workspace API keys, is the one to use — it belongs
to your Granola workspace rather than to a person and keeps working when
people leave). Granola issues API keys on its Business and Enterprise plans;
what Scripe can read is whatever your Granola plan and your key's access
give it.

Afterwards each connection is managed from the knowledge base itself — the
source rows under **External sources**, or the status chip on the source's own
folder row. That screen is where the last run's summary, "Sync now", changing
what is synced and disconnecting all live. Everyone in the workspace can see a
source and what its last run did; "Sync now", changing what is synced and
disconnecting need the workspace admin role, and the screen shows those
controls only to someone who has it.

## What gets synced

| Source           | What is synced                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **GitHub**       | Text files (`.md`, `.mdx`, `.markdown`, `.txt`) in one repository, optionally restricted to one folder. Other files — code, images, lockfiles — are outside the connector's scope and are neither imported nor reported.                                                                                                                                                                                                                                                                                                                                                                                                         |
| **Google Drive** | Exactly the files you pick in Google's own file picker. Google Docs arrive as Markdown, Sheets as CSV, Slides as plain text; `.md`, `.mdx`, `.markdown`, `.txt` and `.csv` files are read as they are.                                                                                                                                                                                                                                                                                                                                                                                                                           |
| **Granola**      | One document per meeting: a header (title, date, participants with their email addresses, organiser, Granola folders, a link back to the note) and the meeting's **AI summary** as Granola wrote it. The **full transcript is off by default** and is a per-connection setting; turning it on stores every word said by everyone in the meeting, rendered as speaker turns with elapsed timestamps, and rewrites every meeting the source holds on its next run. The connection can be narrowed to selected Granola folders (a folder includes everything inside it); with no selection it syncs every meeting the key can read. |

Scripe asks Google only for the narrow per-file permission
(`drive.file`), so it can see the files you pick and nothing else in your
Drive. **Syncing a whole Drive folder is not supported** — that needs a
broader permission we deliberately do not request.

### Limits

| Limit                     | Value                      | What happens at the limit                                                                                                                                                                                           |
| ------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Files per connection      | 2,000                      | How many documents one source can hold. Documents you already have keep their places; once they fill the limit, **new files stop syncing** and the run reports `partial` naming them.                               |
| Total size per connection | 50 MB                      | The same rule by size rather than count. It counts the documents Scripe holds, so a file that was kept after growing past the single-file limit counts what Scripe stored for it, never its new size at the source. |
| Single file               | 2 MB                       | That file is skipped by name and everything else still syncs; a file that was already synced and then grew past the limit is **kept but stops updating**.                                                           |
| Google export             | 10 MB (Google's own limit) | That file fails by name; the run continues.                                                                                                                                                                         |

**Passing a limit never deletes anything.** A document Scripe has already
synced stays in your knowledge base when the source grows past a limit — it
simply **stops updating** until the source shrinks again or you narrow what
the source covers, and the run names it as skipped so you can see which
documents have gone stale. Only genuinely new files past the limit are never
synced at all.

**Because nothing is deleted, the documents you already have hold the
places.** A source at its file limit keeps syncing everything it already
covers, and new files are skipped by name until a place frees up — so
"nothing new appeared" is reported rather than silent. To free a place,
remove the file at the source (delete it in GitHub, or trash or un-pick it
in Drive); that is a real disappearance, so Scripe removes the corresponding
document and the place becomes available on the next run.

A Google Sheet exports **only its first sheet** — that is Google's own
export behaviour, not a Scripe truncation. A multi-tab workbook will sync
its first tab.

## Sync runs and what they report

Every run ends in a summary you can see on the source in the dashboard:

* **Success** — everything the source listed was handled. A file Scripe
  cannot read (a video you picked in Drive, say) is counted and named and
  does not make the run unsuccessful — a real source always holds a few, and
  flagging every run for them would make the flag meaningless. The one
  exception is a file Scripe had **already synced** that it can no longer
  read: that document has stopped updating, so the run is `partial`.
* **Partial** — something you would expect to be synced was not: a file that
  failed, a file over a size limit, files past a connection limit, or a
  document kept but no longer updating because its file passed one of those
  limits. Failed and skipped files are named, so "we ignored 200 of your
  files" is never invisible.
* **Failed** — the run could not be completed at all. Nothing is deleted when
  a run fails, and the next scheduled run tries again.

Runs happen nightly, when you press **Sync now**, and — for GitHub and for
Granola — within moments of a change at the source (a push; a meeting whose
notes were generated or edited). Granola live updates are set up per
connection and only where your Granola workspace offers webhooks; where it
does not, the source says so and syncs nightly. Live updates that could not
be set up are retried on every run, so a source that says they are off can
turn them on by itself once whatever blocked it clears; nightly syncing does
not wait for that and is never affected by it.

## When a file disappears from the source

Deleting a file in GitHub, or trashing or un-picking one in Drive,
**deletes the corresponding document from your knowledge base** on the next
run.

Because that is a deletion, Scripe is careful about what counts as
"disappeared": if a provider is unreachable, rate-limiting us, or returns a
listing it says is incomplete, the run **fails** rather than treating the
missing files as deletions. A file the source still lists is never a
deletion either — including one pushed past a connection limit, which keeps
its document and stops updating (see [Limits](#limits)).

**Un-sharing a Drive file is not the same as deleting it, and Scripe does
not treat it as one.** Drive answers in one of two ways when it will not
hand us a picked file, and neither deletes anything:

* When Drive **refuses** us the file, that answer looks identical whether one
  file was un-shared or Drive itself is refusing every file, so the run
  **fails** and nothing is deleted. The run summary names the file and what
  to do about it: re-share it with the connected Google account, or remove it
  from the source's picked files. Until you do one of those, that source's
  runs keep failing.
* When Drive says it **cannot find** the file, that is also what it says for
  a file the connected account can no longer see — a share that was removed,
  a shared drive you were taken out of — and for a file that was deleted
  outright rather than trashed. Scripe cannot tell those apart, so the
  already-synced document is **kept**, stops updating, and the run reports
  `partial` naming it as unreachable. To remove the document, un-pick the
  file; to resume syncing it, restore access and it picks up on the next run.

Only trashing a file in Drive, or removing it from the source's picked
files, deletes its document.

**A Granola meeting that disappears from the listing is never, by itself,
deleted.** Granola's API only returns notes that have both a summary and a
transcript, and it drops a note from the listing for several reasons that
are not deletion: the note is still being processed, its sharing or the
key's access changed, it moved out of the folders the source is narrowed
to — and, if a transcript retention setting in Granola has removed the
transcript, possibly that too. Granola offers no way to tell those apart
from a real deletion. So a meeting Granola no longer returns is **kept**,
stops updating, and the run reports `partial` naming it as no longer
reachable, on every run, until Granola returns it again. The one exception
is a meeting Granola confirms has **moved out of the folders the source is
narrowed to**: like a file moving out of a GitHub folder path, that meeting's
document is removed. To remove the meetings a source holds, disconnect the
source; reconnecting then imports what Granola currently returns.

A kept meeting still counts against the connection's limits above, and that
has a consequence worth knowing before you rely on it: a Granola workspace
whose own retention keeps purging notes accumulates meetings Scripe keeps
and Granola no longer returns, and once they reach the 2,000-file or 50 MB
limit the source stops importing new meetings. Disconnecting and
reconnecting the source is what clears them today — it removes everything
the source holds and re-imports what Granola currently returns.

A Granola listing that comes back **far smaller** than the previous complete
one — fewer than half the meetings — is refused outright: the run fails
with that reason, nothing is imported or removed, and the source keeps
failing that way each night until you confirm on the source's page that the
change is expected (the key's access changed, or meetings were removed or
moved in Granola). Confirming deletes nothing; it only lets the next run
reconcile against the smaller listing.

Individual synced documents cannot be deleted through the API or the
dashboard — `DELETE /v1/knowledge/{documentId}` answers
[`conflict`](./errors/conflict.md) for them — because the next run would
simply restore the page. Remove the file at the source, or disconnect the
source.

## When a connection stops working

| State      | What it means                                                                                                                                                                                                                                                                                                                                                                                                                        | What to do                                                                                                                                                  |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Broken** | Your grant is gone: the GitHub App was uninstalled or suspended, the repository was removed from it, Google access was revoked or expired, or Granola no longer accepts the API key — it was revoked or rotated, the Granola plan no longer includes API keys, a workspace control withdrew its access, or the person who connected it deleted their Scripe account (the connection and its meetings stay; only the key is removed). | Reconnect the source. For Granola that means pasting a new key into the same source. Documents already synced stay in your knowledge base and keep working. |
| **Paused** | Your plan no longer includes synced knowledge sources.                                                                                                                                                                                                                                                                                                                                                                               | Nothing is deleted, and syncing resumes by itself once your plan includes them again.                                                                       |

A broken or paused connection never silently looks healthy: the source shows
its state and the reason.

A **suspended** GitHub App installation is the one break that repairs
itself: un-suspend it on GitHub and the sources it stopped go back to active
on their own. Sources broken for any other reason are left alone — only the
ones the suspension itself stopped come back.

If the Scripe GitHub App is installed on your account but no source appears
here — the connect flow timed out while you were picking repositories, or you
installed the App from GitHub rather than from Scripe — start the connection
again from Scripe. It recognises the installation you already have and
finishes the connection with it; you never need to uninstall and start over.

## Disconnecting

Disconnecting deletes the documents that source created, along with its
folder, and asks the provider to drop Scripe's access. Scripe asks Google to
revoke the grant and tells you whether that landed — a revoke Google does not
accept leaves the grant standing on your Google account with the source gone
from Scripe, so check your Google account connections if it reports that.
A Granola API key cannot be revoked from Scripe — you created it in Granola
and revoke it there; what Scripe removes is the webhook endpoint it
registered for live updates, and it tells you if Granola did not confirm
that removal.
Disconnecting is never blocked by the provider being down, nor by anything
being wrong with the stored key — a key Scripe can no longer read is one more
reason the webhook endpoint is reported as left standing, never a reason the
source, its folder and its documents stay. The GitHub App
installation is removed only when **no other connected source is still using
it** — one installation can back several
sources, and removing it would stop all of them — so the disconnect tells
you which happened: it was removed, it was kept because your other sources
still use it, or it is still there because removing it did not work. A sync already running
for that source is stopped as part of the disconnect: every document it would
write is checked against the connection first, so once the disconnect has run
it can write nothing behind the cleanup. Connecting the same repository or
Google account again is safe — you get the source back, not a second copy of
it. Deleting your account
or workspace removes both the connection and its documents through the
ordinary deletion path, and a data export includes your connections with
every credential redacted.

## What Scripe stores

* **GitHub:** an installation id. Never a token of yours — each sync mints a
  short-lived token from Scripe's own app key, scoped to the one repository
  the connection names.
* **Google Drive:** a refresh token, stored encrypted (AES-256-GCM), plus the
  list of files you picked. Nothing else from your Drive.
* **Granola:** the API key you pasted, stored encrypted (AES-256-GCM), the
  folders you selected, the transcript setting, and — where live updates
  are on — the signing secret of the webhook endpoint Scripe registered,
  stored the same way. Granola's private notes are never read, whatever the
  key can see.
* **Post history:** nothing new about the posts — they are the LinkedIn posts
  Scripe already syncs for your analytics. The folder's own record is which
  posts you removed from it, and who removed them.

## Post history

In the new Scripe experience, every profile with a connected LinkedIn account
gets a **Post history** folder in the knowledge base the first time someone
opens the knowledge base. It holds one document per published post — the
post's text, verbatim, under one line saying when it was published — and is
kept current by the same nightly sync as the other sources. Nothing needs connecting, no plan feature is required, and the
folder cannot be disconnected: it is part of the profile.

**Removing a post from the folder does not delete the post.** Select posts in
the folder and choose **Remove from knowledge base**. The post stays published
on LinkedIn and stays in your post history and analytics; what changes is
that Scripe stops learning from it for that profile — it is no longer
knowledge, no longer a writing sample for that profile's voice, no longer an
example of past results, and no longer offered to the assistant as one of the
profile's posts. This is the switch for a post about a previous employer or an
audience you no longer write for. Removing is per profile: the same LinkedIn
post linked from two profiles can be removed from one and kept in the other.

Removing and restoring do **not** need the workspace admin role that the
three connectors' controls above need: a profile's post history is curated by
the people who work on that profile, the same population that picks its
writing samples.

Removal is remembered across syncs — a removed post never comes back on its
own — and is reversible: the folder's Synced status lists the removed posts,
each with **Restore**. Restoring puts the post back into everything Scripe
learns from at once; its document returns to the folder with the next sync.

One thing removal does not preserve: if you had hand-picked that post as a
writing sample for the profile's tone of voice, that pick is dropped with the
removal rather than kept aside. Restoring the post brings it back into
everything Scripe learns from, but the pick itself may have to be made again —
a restored post is treated as one Scripe has never been told about, so it is
only picked up as a writing sample again if automatic sample selection is on
and the post is recent enough to qualify. The removal dialog says so.
Switching a writing sample OFF is the opposite case and does survive: a post
you had excluded from the tone of voice stays excluded through removal and
restore, so restoring never brings back a sample you had turned off.

The Synced status lists the most recently removed posts; if you have removed
more than it shows, it says how many of how many you are looking at.

Two things differ from the other synced sources. Each post in the folder is
an ordinary knowledge document, but it is **not searched by the generators as
knowledge** the way an uploaded document is: those already read the profile's
posts as writing samples and recent posts, and reading the same post again as
retrieved knowledge would count it twice. The assistant's
explicit knowledge search does include them. And a post deleted on LinkedIn
leaves the folder once Scripe's LinkedIn sync has confirmed the deletion —
absence from your own post history is the one absence this connector treats
as final, because it is your own data rather than a provider's listing.
