POST
/meeting-notes
Send a note-taker’s output — raw transcript, summary, AI-enriched recap, action items — against an existing meeting. Designed for webhooks from Fathom, Granola, Otter, Zoom AI Companion, or any in-house note-taker flow (Make / Zapier / custom).
The note attaches to a calendar_event that already exists in Nexor. No orphan notes in v1 — if no meeting resolves, you get a 404.
Meeting resolution
The endpoint tries the following in order, stopping at the first match:
calendar_event_id — direct.
external_calendar_provider + external_calendar_event_id — matches meetings created via POST /meetings with those dedup keys.
meeting_url — exact match on the calendar_event’s meeting URL.
lead_email + meeting_date — lead’s meeting within ±60 minutes of meeting_date.
lead_email alone — the lead’s most recent past meeting. No time-window floor.
If none of these resolve a meeting → 404. Create the meeting first via POST /meetings or pass a valid identifier.
Idempotency
If you supply external_meeting_id together with a provider, replays of the same (provider, external_meeting_id) under your client update the existing row instead of creating duplicates. Safe for webhook retries. The timeline note_added activity fires only on the first insert, not on replays.
Reserved providers
The following values are rejected to protect internal sync pipelines: fireflies, diio, gemini, retell. Pick any other lowercase alphanumeric string (e.g. fathom, granola, otter, manual, zoom_ai). Regex: ^[a-z0-9_-]{1,64}$.
Request Body
All fields are optional unless noted. At minimum you must provide:
- one way to resolve a meeting (
calendar_event_id, external_calendar_event_id+external_calendar_provider, meeting_url, or lead_email), and
- one non-empty content field (
notes, transcript_text, summary, manual_ai_summary, or action_items).
| Field | Type | Description |
|---|
provider | string | Source of the note. Default "manual". Reserved names rejected. |
external_meeting_id | string | Stable id from the note-taker (e.g. Fathom Recording ID). Enables idempotent replay. ≤ 255 chars. |
calendar_event_id | string (uuid) | Direct link — tier 1 resolution. |
external_calendar_provider | string | Pair with external_calendar_event_id — tier 2. |
external_calendar_event_id | string | Provider’s event id from POST /meetings. |
meeting_url | string | Meet / Zoom / Fathom URL — tier 3. |
lead_email | string | Matches the lead (client-scoped); also drives tiers 4 & 5. |
meeting_date | string (ISO 8601) | Used with lead_email for ±60 min fuzzy match. |
meeting_title | string | |
duration_seconds | integer | |
organizer_email | string | Host email. |
notes | string | Raw freeform text. Alias for transcript_text — if both are provided, transcript_text wins. ≤ 1 MB. |
transcript_text | string | Full transcript. ≤ 1 MB. |
summary | string | Notetaker’s native summary (Fathom / Granola / Otter recap). |
manual_ai_summary | string | AI-enriched custom summary (e.g. downstream ChatGPT step in Make). Stored separately from summary. |
action_items | array | ["Send pricing", ...] or [{ text, assignee }, ...]. ≤ 512 KB. |
questions | array | ["What's the monthly cost?", ...]. ≤ 512 KB. |
keywords | array | ["pricing", "enterprise", ...]. ≤ 512 KB. |
sentences | array | Speaker-level utterances (Fireflies-compatible shape). ≤ 512 KB. |
status | string | completed (default), pending, or failed. |
Request Body — minimal freeform
Ideal for Make / Zapier scenarios that build a markdown blob and attach it to the lead’s most recent meeting:
{
"lead_email": "tony.stark@gmail.com",
"notes": "Revisamos propuesta. Cliente preguntó por plazos de implementación. Budget Q2 aprobado.",
"manual_ai_summary": "Alta intención. Enterprise. Siguiente paso: propuesta formal antes del viernes."
}
Request Body — Fathom webhook via Make
Using Fathom’s “Watch New Recordings” trigger output mapped to API fields:
{
"provider": "fathom",
"external_meeting_id": "133148102",
"meeting_url": "https://fathom.video/call/133148102",
"meeting_title": "Discovery — Stark Industries",
"meeting_date": "2026-04-15T17:18:02Z",
"organizer_email": "pepper@starkindustries.com",
"lead_email": "tony.stark@gmail.com",
"summary": "## Deal Information\n### Prospect\n[...Fathom markdown summary...]",
"manual_ai_summary": "ChatGPT-enriched recap: strong buying intent, Q2 budget confirmed.",
"action_items": ["Send pricing by Friday", "Intro CTO for technical deep-dive"]
}
Resolving lead_email in Make: Fathom’s is_external flag is unreliable (the host sometimes gets flagged external). Filter Calendar Invitees[] by domain instead:
Array filter:
Input: Calendar Invitees[]
Condition: email_domain ≠ {{Recorded By.email_domain}}
Then:
lead_email = {{first(filtered).Email}}
Example Request
curl -X POST https://api.getnexor.ai/api/public/meeting-notes \
-H "X-API-Key: nxr_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"lead_email": "tony.stark@gmail.com",
"notes": "Revisamos propuesta. Budget Q2 aprobado. Interés Enterprise.",
"manual_ai_summary": "Alta intención. Siguiente paso: propuesta formal antes del viernes."
}'
Response — 201 Created (or 200 on replay upsert)
{
"success": true,
"upserted": false,
"meeting_note": {
"id": "0f9230e0-a5e3-4245-8995-afc61007c992",
"client_id": "2c0911d1-25f9-47c2-8795-fe9407b8d67e",
"calendar_event_id": "5a76ac77-523d-4100-843c-1105212de877",
"lead_id": "0433aeee-44af-4148-9ca0-9e2ac41501a7",
"provider": "manual",
"external_meeting_id": null,
"meeting_title": null,
"meeting_date": "2025-12-29T17:00:00+00:00",
"duration_seconds": null,
"organizer_email": null,
"summary": null,
"manual_ai_summary": "Alta intención. Siguiente paso: propuesta formal antes del viernes.",
"transcript_text": "Revisamos propuesta. Budget Q2 aprobado. Interés Enterprise.",
"action_items": null,
"questions": null,
"keywords": null,
"status": "completed",
"created_at": "2026-04-15T20:52:35.426356+00:00",
"updated_at": "2026-04-15T20:52:35.426356+00:00"
}
}
upserted is true when a prior row with the same (provider, external_meeting_id) was updated instead of a new row being inserted.
Timeline
On first insert, a note_added activity is written to the lead’s timeline with a link to the created meeting_transcripts row. Replays do not emit duplicate activities.
Error Codes
| Code | Meaning |
|---|
400 | Missing or invalid field; reserved provider; payload over size limits; no content fields; nothing to resolve the meeting with |
401 | Missing or invalid X-API-Key |
404 | No calendar_event resolved via any of the 5 strategies |
409 | (provider, external_meeting_id) already bound to a different calendar_event |
500 | Unexpected server error |
Last modified on June 18, 2026