Otter.ai Sensor
What it ingests
Meeting conversations from Otter's Public API (https://api.otter.ai/v1). One conversation becomes:
| Otter data | Ontology entity / edge | Hydration |
|---|---|---|
| The conversation | Meeting, named <title> (<start time>) | deterministic |
| The transcript | CreativeWork (source_type: transcript), Meeting PRODUCED it | deterministic |
| The conversation owner and every named speaker | Person via identity hints, ATTENDED the Meeting | deterministic |
| Calendar guests who did not speak | Person, INVITED_TO the Meeting | deterministic |
| The bullets of Otter's Decisions insight | Decision, Meeting DECIDED_ON it | deterministic |
| Otter's action items | ActionItem, Meeting PRODUCED it; Person ASSIGNED_TO it when Otter's assignee is an attendee | deterministic |
| Topics, projects, organizations, technologies | from the transcript | LLM |
The signal is conversation. Every row above is a binding you can turn off from the source's bindings.
Decisions and action items come from Otter's own AI notes, so they cost no tokens and match what Otter shows. Action items keep Otter's assignee: the item is linked to that person when they spoke in the meeting or own the conversation. The LLM pass over the transcript adds topics and anything Otter's notes left out. A decision both paths find lands on one Decision, not two.
Each conversation is stored once, in full, in sensor_events.content: Otter's summary, insights and outline, the action items, then the speaker-tagged transcript. Secrets spoken in a meeting (API keys, tokens, private-key blocks) are redacted before storage.
Otter uses the same mapping as Granola, so a meeting looks the same in the graph whichever notetaker recorded it.
Credential
References an otter_api_key credential by id. The secret is an Otter API key, sent as Authorization: Bearer <key>.
curl -sS -X POST https://your-corveil-host/api/credentials \
-H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
-d '{"type":"otter_api_key","name":"acme-otter","secret":"<otter api key>"}'Otter's Public API is for Enterprise workspaces only. If the Developer tab is missing under Integrations, ask your Otter account manager to turn it on.
The key acts as the user who created it. It reads that user's own conversations, plus, depending on the config below, conversations shared with them or one channel's conversations. To cover a team, use a dedicated user (for example notes-bot@yourco.com) that your team's conversations are shared with, or an auto-share channel that user belongs to.
Config fields
| Key | Required | Notes |
|---|---|---|
channel_id | no | An Otter channel id. Ingests only that channel's conversations. Overrides include_shared. |
include_shared | no | true also reads conversations other people shared with the key's user. Default false (the user's own conversations). |
A malformed channel id, or an include_shared other than true/false, is rejected when you save the source.
There is no default_entity_type. This source emits several primary types (Meeting, transcript, decisions, action items), so one override would mistype the rest.
Provider-side setup
- Sign in to otter.ai as the user whose conversations Corveil should read.
- Click Integrations in the left navigation and open the Developer tab.
- Click Create key, then Copy. Otter shows the key once. Each user can have two keys.
- Store the key in Corveil as an
otter_api_keycredential. Store only the key, notBearer …. - Optional: to ingest one channel, for example a public auto-share channel for customer calls, set
channel_idto its id.
No webhooks, OAuth app, or admin consent are needed.
Wire-up
POST /api/sensors
{
"sensor_type": "otter",
"name": "Acme Otter",
"config": {
"include_shared": "true"
},
"credentials": { "otter_credential": "<otter_api_key credential id>" }
}Verify
Run Test connection. It lists one conversation with the key, so a revoked or mistyped key, or a workspace without the Public API, fails at save rather than on the first poll.
Once polling, the per-poll summary log shows conversations_listed, events, and the skip counts (skipped_ingested, skipped_pending, skipped_gone, skipped_empty). In the ontology, look for Meeting entities with notetaker: otter. Each one's neighborhood should hold its speakers, its transcript, its decisions, and its action items.
How incremental sync works
- The first poll seeds a watermark and ingests nothing. History comes from the one-time backfill (
SENSOR_BACKFILL_SINCE, default 72 hours), which reads conversations by creation date. - Otter has no date filter and no "updated" timestamp. Its list is newest first, so each poll walks it down to the last watermark minus a 24-hour settle window, and fetches only the conversations it has not ingested yet.
- A conversation is ingested once Otter finishes it. Otter lists a conversation as soon as recording starts, but writes the summary and action items after the meeting ends. While Otter reports it still processing, the sensor waits and picks it up on a later poll inside the settle window. A conversation Otter never summarizes is ingested 6 hours after it was created, so a meeting still being recorded is never ingested half-transcribed.
- A busy window is never skipped. One poll reads at most 2,000 conversations. If there are more, the watermark stays where it was and the next poll re-reads the window. A backfill that hits its ceiling fails and is retried.
- Deletes and empty conversations. Conversations that return 404 between the list and the fetch, and finished conversations with neither a summary nor a transcript, are skipped once and not fetched again. Entities from earlier ingests are not removed.
- Rate limits. Otter allows Enterprise keys 10 requests per second. The sensor paces itself under that. On a
429it waits (Retry-After, or 2 seconds) and retries up to 3 times, then stops the poll without moving the watermark.
Known limits
- Edits after ingest are not picked up. Otter exposes no "updated" time, so a conversation is read once, after Otter finishes it. A later retitle, a regenerated summary, or an action item marked done in Otter does not reach Corveil.
- No calendar merge. Otter returns the calendar guests and the meeting join link, but not the calendar event id, so Otter Meetings are not linked to Google Calendar or Outlook Calendar Events. Guests still appear as
INVITED_TO. - Speakers. Otter names a speaker only once someone tagged the voice. Untagged voices (
Speaker 2,Unknown Speaker) stay in the transcript but are not people. A speaker gets an email when their name matches the owner, a calendar guest, or an action-item assignee. - Action-item owners are linked only when Otter's assignee is an attendee (the owner or a named speaker). For anyone else, the assignee's name is kept next to the item in the stored content, with no edge.
- Decisions come only from an insight topic named Decisions (or Key decisions / Agreements). Which insight topics Otter writes depends on the workspace's meeting template; without one, decisions come from the LLM pass alone.
- A conversation still processing when it leaves the 24-hour settle window is not ingested.
- Not included: audio, channels as entities, custom-prompt output, Otter webhooks, and super-admin workspace-wide listing.