An AI notetaker that needs your calendar can only hear the calls you scheduled.
By Kyle Nelson, Founder, Fazit
A bot notetaker cannot join a call it was never told about, and the way it gets told is by reading your calendar. So an AI notetaker without calendar access isn’t a stripped-down version of the real thing: it’s the only kind that can hear a WhatsApp call, a Slack huddle, or the client who rings twenty minutes before the invite says they will.
What a calendar integration actually grants
In these products the calendar is the dispatch mechanism, not a convenience feature. To join a meeting a bot needs a joinable URL, and to find one it subscribes to your calendar and parses event bodies for links. That subscription is an OAuth grant, usually https://www.googleapis.com/auth/calendar.readonly on Google or Calendars.Read through Microsoft Graph.
Read the scope literally. It returns the calendar, not the subset of meetings that have video links: every event title, every attendee address, every description and location, on a continuous sync.
So if the bot joins six calls a week and your calendar holds forty events, the vendor is processing all forty. That includes the deposition prep, the medical appointment, the event titled with a client name that has not been announced yet, and the interview with a candidate who does not know your notetaker vendor exists.
WHAT THE BOT NEEDS VS WHAT THE SCOPE RETURNS
what it needs what calendar.readonly returns
one join URL, every event, every day
six times a week event titles
attendee email addresses
descriptions and locations
on a continuous sync
The distance between those two columns is the part
your Art. 30 record has to describe.There is a second-order effect that shows up in procurement. Calendar attendee lists are how a vendor learns your client roster without transcribing a word. For a consultancy under NDA, or a firm whose client list is itself confidential, that is a disclosure question independent of anything said on the calls. The contractual side of it is in the NDA post.
The calls that never have an invite
Once the dispatch mechanism is a calendar link, the coverage gap is predictable. These calls have no URL for a bot to join:
- A WhatsApp Desktop voice call. End to end encrypted, no join link, no participant API.
- A Slack huddle, started from a channel with no dial-in and no invite.
- A FaceTime call, including one that started on your phone.
- A phone call on speaker next to the laptop.
- An ad-hoc Zoom meet-now, or a Microsoft Teams call started from a chat window rather than a calendar event.
- The conversation in the room, with the laptop open on the table.
For anyone doing hybrid client work, that is not the tail of the distribution. A consultant’s Tuesday is a scheduled Zoom kickoff, then the client calling back on WhatsApp because they are in a taxi, then a huddle with the delivery team, then forty minutes in a room with a whiteboard. One of those four has an invite.
Several vendors noticed this and shipped a desktop app that captures without a bot. That closes the coverage gap and leaves the harder question open: where the audio goes once it has been captured. Bot-free is not the same as private is the longer version of that argument.
Capture at the audio layer instead
macOS 14.4 introduced Core Audio process taps, which let one application receive the audio another application is playing. Fazit uses them for the other party’s voice and AVAudioEngine for your own microphone. Neither path knows or cares which app is on the other end.
// ProcessTap.swift — the other party's stream let tapDescription = CATapDescription(monoMixdownOfProcesses: processObjects) tapDescription.isPrivate = true // not exposed system-wide tapDescription.muteBehavior = .unmuted // the call keeps playing normally var newTap: AUAudioObjectID = kAudioObjectUnknown try check(AudioHardwareCreateProcessTap(tapDescription, &newTap))
Three details matter more than the API surface.
The tap reads the decrypted output after it reaches the speakers, so end-to-end encryption on the call itself is untouched and irrelevant. WhatsApp’s encryption protects the wire. What comes out of the speaker is sound, and sound is what gets tapped.
The tap uses the system-audio-recording permission, declared with NSAudioCaptureUsageDescription, which is separate from screen recording. That matters on macOS Sequoia, which added a monthly re-consent prompt for screen-recording access. Tools that capture system audio through ScreenCaptureKit inherit that prompt; a process tap does not.
processObjects is a list rather than a single PID, because browsers and Electron apps render audio in helper processes. Translating only the main PID taps an object that never produces a sample, so Fazit matches the whole bundle family instead, letting com.google.Chrome also collect com.google.Chrome.helper. The tap and aggregate-device plumbing is adapted from Guilherme Rambo’s insidegui/AudioCap under BSD-2-Clause, still the clearest public reference for this API.
To Core Audio, a call app is a PID that is producing sound. Zoom, WhatsApp Desktop, Slack, FaceTime and a browser tab are the same kind of object, which is why the coverage question disappears rather than getting solved app by app. There was never a per-app integration to be missing.
Both streams land in one place: an in-memory ring buffer at 16 kHz mono Float32, about 1.9 MB per minute, capped and then overwritten. There is no write(to:) on that type, and adding one would end the product. Each note carries audio_retained: false for the same reason. The security page has the full set of invariants, and why “never records” is an invariant has the argument behind them.
How the app knows a call started, without reading anything
Detection is the other half of what the calendar was doing. Fazit replaces it with a single status bit.
kAudioDevicePropertyDeviceIsRunningSomewhere on the default input device answers one question: is any process currently using the microphone. It is a boolean, and it carries no audio, no process identity and no content.
// CallDetector.swift — a status flag, not a sample
var addr = AudioObjectPropertyAddress(
mSelector: kAudioDevicePropertyDeviceIsRunningSomewhere,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain
)That bit is watched with AudioObjectAddPropertyListenerBlock, so the code runs when the state changes and never otherwise. No polling loop, no CPU while nothing is happening, and an immediate reaction when a call starts. The only timer in the file is a single 15-second check after the mic goes quiet, used to decide the call is over.
To pick which process to tap, Fazit reads the running application list and matches bundle identifiers against call-capable names, browsers included, since Google Meet and Teams calls live in tabs.
What is not read is the interesting part: no calendar scope, no email, no browser tabs or page content, no accessibility tree, no screen. The app knows the microphone is busy and which application is in front. That is the whole input.
What this costs you
Three honest limits, because the trade is real.
A browser tap is application-wide. The tap is a mono mixdown of that app’s processes, so a podcast playing in another Chrome tab joins the mixdown. In practice you close the tab. In principle, per-tab isolation is not something Core Audio offers.
Detection is a heuristic, not an oracle. The candidate list covers Zoom, Teams, WhatsApp, Webex, Slack, Discord, FaceTime and the major browsers. Anything outside it starts from the global hotkey instead, which checks the mic bit directly and taps whatever is running.
Nothing can be re-run later. Cloud products keep your audio, so they can reprocess it when their model improves. Fazit transcribes during the call and zeroes the samples at the end. If a better model ships next year it gets your future calls and not your past ones. That is the cost of audio_retained: false, and it is the right trade for privileged conversations, but it is a cost.
The requirements follow from the same architecture: macOS 14.4 or later, since process taps do not exist before it, on Apple Silicon, since transcription runs Parakeet through CoreML on the Neural Engine.
The note is the same file either way
Whatever app the call happened in, the output is one Markdown file in your Obsidian vault, named the way you name things: Sarah — pricing.md. A WhatsApp call and a scheduled Zoom produce the same artifact in the same folder, because the note format was never coupled to the transport.
That also answers the metadata question from the first section. The note has the participant’s name because you wrote it, not because a vendor synced your calendar to learn it. More on why files beat rows in Obsidian meeting notes should be files.
FAQ
Is there an AI notetaker that works without calendar access?
Yes. Notetakers that capture at the operating-system audio layer do not need a calendar, because they are not dispatching a bot to a meeting URL. Fazit uses Core Audio process taps on macOS 14.4 and later to capture the call app's output directly, plus the local microphone, and detects that a call is happening from a single system status bit reporting whether the mic is in use. No OAuth grant to Google Calendar or Microsoft 365 is involved.
Why do AI notetakers ask for calendar permission?
Because a meeting bot needs a joinable link, and the link lives in the calendar event. The integration reads your events, finds Zoom, Google Meet or Microsoft Teams URLs, and dispatches a bot participant at the scheduled time. The side effect is that the vendor receives every event on the calendar, including titles and attendee email addresses for meetings the bot never joins.
Can an AI notetaker transcribe a WhatsApp call?
A bot-based one cannot, because WhatsApp calls are end-to-end encrypted with no join link and no participant API for a bot to use. A notetaker that captures at the audio layer can, because it taps the decrypted audio after it reaches the speakers, which is the same sound you hear. The encryption of the call itself is unaffected. Fazit treats WhatsApp Desktop the same as Zoom: a process producing sound.
Can anything take notes on a Slack huddle?
Not a meeting bot, since a huddle has no dial-in URL for one to join. A local capture tool can, because Slack is an application playing audio like any other. This is the general shape of the answer for FaceTime, ad-hoc Zoom meet-now calls, and Microsoft Teams calls started from a chat window rather than a calendar event.
Does capturing system audio this way need screen-recording permission on macOS?
No. Core Audio process taps use a separate system-audio-recording permission, declared with NSAudioCaptureUsageDescription in the app's Info.plist. That distinction matters on macOS Sequoia, which added a monthly re-consent prompt for screen-recording access. Tools that capture system audio through ScreenCaptureKit inherit that prompt; a process tap does not.